5706 lines
179 KiB
C++
5706 lines
179 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "STimingView.h"
|
|
|
|
#include "Containers/ArrayBuilder.h"
|
|
#include "Containers/MapBuilder.h"
|
|
#include "Features/IModularFeatures.h"
|
|
#include "Fonts/FontMeasure.h"
|
|
#include "Fonts/SlateFontInfo.h"
|
|
#include "Framework/Application/MenuStack.h"
|
|
#include "Framework/Application/SlateApplication.h"
|
|
#include "Framework/Commands/Commands.h"
|
|
#include "Framework/Commands/UICommandList.h"
|
|
#include "Framework/MultiBox/MultiBoxBuilder.h"
|
|
#include "HAL/PlatformApplicationMisc.h"
|
|
#include "HAL/PlatformTime.h"
|
|
#include "Layout/WidgetPath.h"
|
|
#include "Logging/MessageLog.h"
|
|
#include "Misc/Paths.h"
|
|
#include "Rendering/DrawElements.h"
|
|
#include "SlateOptMacros.h"
|
|
#include "Styling/AppStyle.h"
|
|
#include "Styling/SlateBrush.h"
|
|
#include "Styling/StyleColors.h"
|
|
#include "Styling/ToolBarStyle.h"
|
|
#include "Widgets/Docking/SDockTab.h"
|
|
#include "Widgets/Input/SButton.h"
|
|
#include "Widgets/Input/SCheckBox.h"
|
|
#include "Widgets/Input/SComboButton.h"
|
|
#include "Widgets/Input/SEditableTextBox.h"
|
|
#include "Widgets/Input/SSegmentedControl.h"
|
|
#include "Widgets/Layout/SScrollBar.h"
|
|
#include "Widgets/Layout/SSpacer.h"
|
|
#include "Widgets/SOverlay.h"
|
|
#include "Widgets/Text/STextBlock.h"
|
|
|
|
// TraceServices
|
|
#include "TraceServices/Model/LoadTimeProfiler.h"
|
|
|
|
// TraceInsightsCore
|
|
#include "InsightsCore/Common/PaintUtils.h"
|
|
#include "InsightsCore/Common/Stopwatch.h"
|
|
#include "InsightsCore/Common/TimeUtils.h"
|
|
#include "InsightsCore/Filter/ViewModels/FilterConfigurator.h"
|
|
#include "InsightsCore/Filter/ViewModels/TimeFilterValueConverter.h"
|
|
#include "InsightsCore/Table/Widgets/STableTreeView.h"
|
|
|
|
// TraceInsights
|
|
#include "Insights/Common/InsightsMenuBuilder.h"
|
|
#include "Insights/Config.h"
|
|
#include "Insights/InsightsManager.h"
|
|
#include "Insights/InsightsStyle.h"
|
|
#include "Insights/ITimingViewExtender.h"
|
|
#include "Insights/LoadingProfiler/LoadingProfilerManager.h"
|
|
#include "Insights/LoadingProfiler/Tracks/LoadingTimingTrack.h"
|
|
#include "Insights/LoadingProfiler/ViewModels/LoadingSharedState.h"
|
|
#include "Insights/LoadingProfiler/Widgets/SLoadingProfilerWindow.h"
|
|
#include "Insights/Log.h"
|
|
#include "Insights/TaskGraphProfiler/TaskGraphProfilerManager.h"
|
|
#include "Insights/Tests/TimingProfilerTests.h"
|
|
#include "Insights/TimingProfiler/TimingProfilerManager.h"
|
|
#include "Insights/TimingProfiler/Tracks/FileActivityTimingTrack.h"
|
|
#include "Insights/TimingProfiler/Tracks/MarkersTimingTrack.h"
|
|
#include "Insights/TimingProfiler/Tracks/RegionsTimingTrack.h"
|
|
#include "Insights/TimingProfiler/Tracks/ThreadTimingTrack.h"
|
|
#include "Insights/TimingProfiler/Tracks/TimeRulerTrack.h"
|
|
#include "Insights/TimingProfiler/ViewModels/FileActivitySharedState.h"
|
|
#include "Insights/TimingProfiler/ViewModels/FrameTimingTrack.h"
|
|
#include "Insights/TimingProfiler/ViewModels/ThreadTimingSharedState.h"
|
|
#include "Insights/TimingProfiler/ViewModels/TimeMarker.h"
|
|
#include "Insights/TimingProfiler/ViewModels/TimerFilters.h"
|
|
#include "Insights/TimingProfiler/ViewModels/TimingRegionsSharedState.h"
|
|
#include "Insights/TimingProfiler/Widgets/SStatsView.h"
|
|
#include "Insights/TimingProfiler/Widgets/STimersView.h"
|
|
#include "Insights/TimingProfiler/Widgets/STimingProfilerWindow.h"
|
|
#include "Insights/TimingProfilerCommon.h"
|
|
#include "Insights/ViewModels/BaseTimingTrack.h"
|
|
#include "Insights/ViewModels/DrawHelpers.h"
|
|
#include "Insights/ViewModels/GraphSeries.h"
|
|
#include "Insights/ViewModels/GraphTrack.h"
|
|
#include "Insights/ViewModels/QuickFind.h"
|
|
#include "Insights/ViewModels/ThreadTrackEvent.h"
|
|
#include "Insights/ViewModels/TimingEventSearch.h"
|
|
#include "Insights/ViewModels/TimingGraphTrack.h"
|
|
#include "Insights/ViewModels/TimingViewDrawHelper.h"
|
|
#include "Insights/Widgets/SLogView.h"
|
|
#include "Insights/Widgets/SQuickFind.h"
|
|
#include "Insights/Widgets/STimingViewTrackList.h"
|
|
|
|
#include <limits>
|
|
|
|
#define LOCTEXT_NAMESPACE "UE::Insights::TimingProfiler::STimingView"
|
|
|
|
#define INSIGHTS_ACTIVATE_BENCHMARK 0
|
|
|
|
const TCHAR* GetFileActivityTypeName(TraceServices::EFileActivityType Type);
|
|
uint32 GetFileActivityTypeColor(TraceServices::EFileActivityType Type);
|
|
|
|
namespace UE::Insights::Timing
|
|
{
|
|
const FName TimingViewExtenderFeatureName(TEXT("TimingViewExtender"));
|
|
}
|
|
#if UE_INSIGHTS_BACKWARD_COMPATIBILITY_UE54
|
|
namespace Insights
|
|
{
|
|
const FName TimingViewExtenderFeatureName(TEXT("TimingViewExtenderOld"));
|
|
}
|
|
namespace UE::Insights
|
|
{
|
|
const FName TimingViewExtenderFeatureName(TEXT("TimingViewExtenderOld"));
|
|
}
|
|
#endif // UE_INSIGHTS_BACKWARD_COMPATIBILITY_UE54
|
|
|
|
namespace UE::Insights::TimingProfiler
|
|
{
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
uint32 STimingView::TimingViewId = 0;
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
STimingView::STimingView()
|
|
: bScrollableTracksOrderIsDirty(false)
|
|
, FrameSharedState(MakeShared<FFrameSharedState>(this))
|
|
, ThreadTimingSharedState(MakeShared<FThreadTimingSharedState>(this))
|
|
, LoadingSharedState(MakeShared<LoadingProfiler::FLoadingSharedState>(this))
|
|
, FileActivitySharedState(MakeShared<FFileActivitySharedState>(this))
|
|
, TimingRegionsSharedState(MakeShared<FTimingRegionsSharedState>(this))
|
|
, TimeRulerTrack(MakeShared<FTimeRulerTrack>())
|
|
, DefaultTimeMarker(MakeShared<FTimeMarker>())
|
|
, MarkersTrack(MakeShared<FMarkersTimingTrack>())
|
|
, bAllowPanningOnScreenEdges(false)
|
|
, DPIScaleFactor(1.0f)
|
|
, EdgeFrameCountX(0)
|
|
, EdgeFrameCountY(0)
|
|
, WhiteBrush(FInsightsStyle::Get().GetBrush("WhiteBrush"))
|
|
, MainFont(FAppStyle::Get().GetFontStyle("SmallFont"))
|
|
, QuickFindTabId(TEXT("QuickFind"), TimingViewId++)
|
|
{
|
|
DefaultTimeMarker->SetName(TEXT(""));
|
|
DefaultTimeMarker->SetColor(FLinearColor(0.85f, 0.5f, 0.03f, 0.5f));
|
|
|
|
IModularFeatures::Get().RegisterModularFeature(Timing::TimingViewExtenderFeatureName, FrameSharedState.Get());
|
|
IModularFeatures::Get().RegisterModularFeature(Timing::TimingViewExtenderFeatureName, ThreadTimingSharedState.Get());
|
|
IModularFeatures::Get().RegisterModularFeature(Timing::TimingViewExtenderFeatureName, LoadingSharedState.Get());
|
|
IModularFeatures::Get().RegisterModularFeature(Timing::TimingViewExtenderFeatureName, FileActivitySharedState.Get());
|
|
IModularFeatures::Get().RegisterModularFeature(Timing::TimingViewExtenderFeatureName, TimingRegionsSharedState.Get());
|
|
|
|
ExtensionOverlay = SNew(SOverlay).Visibility(EVisibility::SelfHitTestInvisible);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
STimingView::~STimingView()
|
|
{
|
|
AllTracks.Reset();
|
|
TopDockedTracks.Reset();
|
|
BottomDockedTracks.Reset();
|
|
ScrollableTracks.Reset();
|
|
ForegroundTracks.Reset();
|
|
|
|
SelectedEvent.Reset();
|
|
|
|
for (Timing::ITimingViewExtender* Extender : GetExtenders())
|
|
{
|
|
Extender->OnEndSession(*this);
|
|
}
|
|
#if UE_INSIGHTS_BACKWARD_COMPATIBILITY_UE54
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
::Insights::ITimingViewSession* CurrentTimingViewSession = (::Insights::ITimingViewSession*)(Timing::ITimingViewSession*)this;
|
|
for (::Insights::ITimingViewExtender* Extender : GetOldExtenders())
|
|
{
|
|
Extender->OnEndSession(*CurrentTimingViewSession);
|
|
}
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
#endif // UE_INSIGHTS_BACKWARD_COMPATIBILITY_UE54
|
|
|
|
IModularFeatures::Get().UnregisterModularFeature(Timing::TimingViewExtenderFeatureName, TimingRegionsSharedState.Get());
|
|
IModularFeatures::Get().UnregisterModularFeature(Timing::TimingViewExtenderFeatureName, FileActivitySharedState.Get());
|
|
IModularFeatures::Get().UnregisterModularFeature(Timing::TimingViewExtenderFeatureName, LoadingSharedState.Get());
|
|
IModularFeatures::Get().UnregisterModularFeature(Timing::TimingViewExtenderFeatureName, ThreadTimingSharedState.Get());
|
|
IModularFeatures::Get().UnregisterModularFeature(Timing::TimingViewExtenderFeatureName, FrameSharedState.Get());
|
|
|
|
FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(QuickFindTabId);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::Construct(const FArguments& InArgs, FName InViewName)
|
|
{
|
|
ViewName = InViewName;
|
|
|
|
GraphTrack = MakeShared<FTimingGraphTrack>(SharedThis(this));
|
|
GraphTrack->SetName(TEXT("Main Graph"));
|
|
|
|
FSlimHorizontalToolBarBuilder LeftToolbar(nullptr, FMultiBoxCustomization::None);
|
|
LeftToolbar.SetStyle(&FInsightsStyle::Get(), "SecondaryToolbar");
|
|
|
|
LeftToolbar.BeginSection("Menus");
|
|
LeftToolbar.AddComboButton(
|
|
FUIAction(),
|
|
FOnGetContent::CreateSP(this, &STimingView::MakeAllTracksMenu),
|
|
LOCTEXT("AllTracksMenu", "All Tracks"),
|
|
LOCTEXT("AllTracksMenuToolTip", "The list of all available tracks"),
|
|
FSlateIcon(FInsightsStyle::GetStyleSetName(), "Icons.AllTracksMenu.ToolBar"));
|
|
LeftToolbar.AddComboButton(
|
|
FUIAction(),
|
|
FOnGetContent::CreateSP(this, &STimingView::MakeCpuGpuTracksFilterMenu),
|
|
LOCTEXT("CpuGpuTracksMenu", "CPU/GPU"),
|
|
LOCTEXT("CpuGpuTracksMenuToolTip", "The CPU/GPU timing tracks"),
|
|
FSlateIcon(FInsightsStyle::GetStyleSetName(), "Icons.CpuGpuTracksMenu.ToolBar"));
|
|
LeftToolbar.AddComboButton(
|
|
FUIAction(),
|
|
FOnGetContent::CreateSP(this, &STimingView::MakeOtherTracksFilterMenu),
|
|
LOCTEXT("OtherTracksMenu", "Other"),
|
|
LOCTEXT("OtherTracksMenuToolTip", "Other type of tracks"),
|
|
FSlateIcon(FInsightsStyle::GetStyleSetName(), "Icons.OtherTracksMenu.ToolBar"));
|
|
LeftToolbar.AddComboButton(
|
|
FUIAction(),
|
|
FOnGetContent::CreateSP(this, &STimingView::MakePluginTracksFilterMenu),
|
|
LOCTEXT("PluginTracksMenu", "Plugins"),
|
|
LOCTEXT("PluginTracksMenuToolTip", "Tracks added by plugins"),
|
|
FSlateIcon(FInsightsStyle::GetStyleSetName(), "Icons.PluginTracksMenu.ToolBar"));
|
|
LeftToolbar.AddComboButton(
|
|
FUIAction(),
|
|
FOnGetContent::CreateSP(this, &STimingView::MakeViewModeMenu),
|
|
LOCTEXT("ViewModeMenu", "View Mode"),
|
|
LOCTEXT("ViewModeMenuToolTip", "Various options for the Timing view"),
|
|
FSlateIcon(FInsightsStyle::GetStyleSetName(), "Icons.ViewModeMenu.ToolBar"));
|
|
LeftToolbar.EndSection();
|
|
|
|
//////////////////////////////////////////////////
|
|
|
|
FSlimHorizontalToolBarBuilder RightToolbar(nullptr, FMultiBoxCustomization::None);
|
|
RightToolbar.SetStyle(&FInsightsStyle::Get(), "SecondaryToolbar2");
|
|
|
|
FUIAction AutoScrollToggleButtonAction;
|
|
AutoScrollToggleButtonAction.GetActionCheckState.BindLambda([this]
|
|
{
|
|
return bAutoScroll ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
|
|
});
|
|
AutoScrollToggleButtonAction.ExecuteAction.BindLambda([this]
|
|
{
|
|
SetAutoScroll(!bAutoScroll);
|
|
Viewport.AddDirtyFlags(ETimingTrackViewportDirtyFlags::HInvalidated);
|
|
});
|
|
|
|
RightToolbar.BeginSection("Auto-Scroll");
|
|
RightToolbar.AddToolBarButton(
|
|
AutoScrollToggleButtonAction,
|
|
NAME_None,
|
|
TAttribute<FText>(),
|
|
LOCTEXT("AutoScrollToolTip", "Auto-Scroll"),
|
|
FSlateIcon(FInsightsStyle::GetStyleSetName(),"Icons.AutoScroll"),
|
|
EUserInterfaceActionType::ToggleButton);
|
|
RightToolbar.AddComboButton(
|
|
FUIAction(),
|
|
FOnGetContent::CreateSP(this, &STimingView::MakeAutoScrollOptionsMenu),
|
|
TAttribute<FText>(),
|
|
LOCTEXT("AutoScrollOptionsToolTip", "Auto-Scroll Options"),
|
|
TAttribute<FSlateIcon>(),
|
|
true);
|
|
RightToolbar.EndSection();
|
|
|
|
//////////////////////////////////////////////////
|
|
|
|
ChildSlot
|
|
[
|
|
SNew(SOverlay)
|
|
.Visibility(EVisibility::SelfHitTestInvisible)
|
|
|
|
+ SOverlay::Slot()
|
|
.VAlign(VAlign_Bottom)
|
|
.Padding(FMargin(0.0f, 0.0f, 8.0f, 0.0f))
|
|
[
|
|
SAssignNew(HorizontalScrollBar, SScrollBar)
|
|
.Orientation(Orient_Horizontal)
|
|
.AlwaysShowScrollbar(false)
|
|
.Visibility(EVisibility::Visible)
|
|
.OnUserScrolled(this, &STimingView::HorizontalScrollBar_OnUserScrolled)
|
|
]
|
|
|
|
+ SOverlay::Slot()
|
|
.HAlign(HAlign_Right)
|
|
.Padding(FMargin(0.0f, 0.0f, 2.0f, 0.0f))
|
|
[
|
|
SAssignNew(VerticalScrollBar, SScrollBar)
|
|
.Orientation(Orient_Vertical)
|
|
.AlwaysShowScrollbar(false)
|
|
.Visibility(EVisibility::Visible)
|
|
.OnUserScrolled(this, &STimingView::VerticalScrollBar_OnUserScrolled)
|
|
]
|
|
|
|
+ SOverlay::Slot()
|
|
.HAlign(HAlign_Fill)
|
|
.VAlign(VAlign_Top)
|
|
.Padding(FMargin(0.0f))
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.HAlign(HAlign_Left)
|
|
.FillWidth(1.0f)
|
|
[
|
|
LeftToolbar.MakeWidget()
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.HAlign(HAlign_Right)
|
|
.AutoWidth()
|
|
[
|
|
RightToolbar.MakeWidget()
|
|
]
|
|
]
|
|
|
|
+ SOverlay::Slot()
|
|
.HAlign(HAlign_Fill)
|
|
.VAlign(VAlign_Fill)
|
|
.Padding(FMargin(0.0f))
|
|
[
|
|
ExtensionOverlay.ToSharedRef()
|
|
]
|
|
];
|
|
|
|
UpdateHorizontalScrollBar();
|
|
UpdateVerticalScrollBar();
|
|
|
|
BindCommands();
|
|
|
|
FTabSpawnerEntry& TabSpawnerEntry = FGlobalTabmanager::Get()->RegisterNomadTabSpawner(QuickFindTabId,
|
|
FOnSpawnTab::CreateSP(this, &STimingView::SpawnQuickFindTab))
|
|
.SetDisplayName(LOCTEXT("QuickFindTabTitle", "Quick Find"))
|
|
.SetMenuType(ETabSpawnerMenuType::Hidden)
|
|
.SetIcon(FSlateIcon(FInsightsStyle::GetStyleSetName(), "Icons.Find"));
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::HideAllDefaultTracks()
|
|
{
|
|
FrameSharedState->HideAllFrameTracks();
|
|
ThreadTimingSharedState->HideAllGpuTracks();
|
|
ThreadTimingSharedState->HideAllCpuTracks();
|
|
LoadingSharedState->HideAllLoadingTracks();
|
|
FileActivitySharedState->HideAllIoTracks();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::Reset(bool bIsFirstReset)
|
|
{
|
|
LLM_SCOPE_BYTAG(Insights);
|
|
|
|
const FInsightsSettings& Settings = FInsightsManager::Get()->GetSettings();
|
|
|
|
if (!bIsFirstReset)
|
|
{
|
|
for (Timing::ITimingViewExtender* Extender : GetExtenders())
|
|
{
|
|
Extender->OnEndSession(*this);
|
|
}
|
|
#if UE_INSIGHTS_BACKWARD_COMPATIBILITY_UE54
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
::Insights::ITimingViewSession* CurrentTimingViewSession = (::Insights::ITimingViewSession*)(Timing::ITimingViewSession*)this;
|
|
for (::Insights::ITimingViewExtender* Extender : GetOldExtenders())
|
|
{
|
|
Extender->OnEndSession(*CurrentTimingViewSession);
|
|
}
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
#endif // UE_INSIGHTS_BACKWARD_COMPATIBILITY_UE54
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
|
|
Viewport.Reset();
|
|
|
|
if (IsAutoHideEmptyTracksEnabled() != Settings.IsAutoHideEmptyTracksEnabled())
|
|
{
|
|
ToggleAutoHideEmptyTracks();
|
|
}
|
|
|
|
Viewport.SetScaleX(100.0 / FMath::Clamp(Settings.GetDefaultZoomLevel(), 0.00000001, 3600.0));
|
|
|
|
//////////////////////////////////////////////////
|
|
|
|
for (auto& KV : AllTracks)
|
|
{
|
|
KV.Value->SetLocation(ETimingTrackLocation::None);
|
|
}
|
|
|
|
AllTracks.Reset();
|
|
TopDockedTracks.Reset();
|
|
BottomDockedTracks.Reset();
|
|
ScrollableTracks.Reset();
|
|
ForegroundTracks.Reset();
|
|
|
|
bScrollableTracksOrderIsDirty = false;
|
|
|
|
FTimingEventsTrack::bUseDownSampling = true;
|
|
|
|
//////////////////////////////////////////////////
|
|
|
|
TimeRulerTrack->Reset();
|
|
AddTopDockedTrack(TimeRulerTrack);
|
|
TimeRulerTrack->AddTimeMarker(DefaultTimeMarker);
|
|
SetTimeMarker(std::numeric_limits<double>::infinity());
|
|
|
|
#if 0 // test for multiple time markers
|
|
TSharedRef<FTimeMarker> TimeMarkerA = DefaultTimeMarker;
|
|
TimeMarkerA->SetName(TEXT("A"));
|
|
TimeMarkerA->SetColor(FLinearColor(0.85f, 0.5f, 0.03f, 0.5f));
|
|
|
|
TSharedRef<FTimeMarker> TimeMarkerB = MakeShared<FTimeMarker>();
|
|
TimeRulerTrack->AddTimeMarker(TimeMarkerB);
|
|
TimeMarkerB->SetName(TEXT("B"));
|
|
TimeMarkerB->SetColor(FLinearColor(0.03f, 0.85f, 0.5f, 0.5f));
|
|
|
|
TSharedRef<FTimeMarker> TimeMarkerC = MakeShared<FTimeMarker>();
|
|
TimeRulerTrack->AddTimeMarker(TimeMarkerC);
|
|
TimeMarkerC->SetName(TEXT("C"));
|
|
TimeMarkerC->SetColor(FLinearColor(0.03f, 0.5f, 0.85f, 0.5f));
|
|
|
|
TimeMarkerA->SetTime(0.0f);
|
|
TimeMarkerB->SetTime(1.0f);
|
|
TimeMarkerC->SetTime(2.0f);
|
|
#endif
|
|
|
|
MarkersTrack->Reset();
|
|
AddTopDockedTrack(MarkersTrack);
|
|
|
|
GraphTrack->Reset();
|
|
GraphTrack->SetOrder(FTimingTrackOrder::First);
|
|
constexpr double GraphTrackHeight = 200.0;
|
|
GraphTrack->SetHeight(static_cast<float>(GraphTrackHeight));
|
|
GraphTrack->GetSharedValueViewport().SetBaselineY(GraphTrackHeight - 1.0);
|
|
GraphTrack->GetSharedValueViewport().SetScaleY(GraphTrackHeight / 0.1); // 100ms
|
|
GraphTrack->AddDefaultFrameSeries();
|
|
GraphTrack->SetVisibilityFlag(false);
|
|
AddTopDockedTrack(GraphTrack);
|
|
|
|
//////////////////////////////////////////////////
|
|
|
|
ExtensionOverlay->ClearChildren();
|
|
|
|
//////////////////////////////////////////////////
|
|
|
|
MousePosition = FVector2D::ZeroVector;
|
|
|
|
MousePositionOnButtonDown = FVector2D::ZeroVector;
|
|
ViewportStartTimeOnButtonDown = 0.0;
|
|
ViewportScrollPosYOnButtonDown = 0.0f;
|
|
|
|
MousePositionOnButtonUp = FVector2D::ZeroVector;
|
|
|
|
LastScrollPosY = 0.0f;
|
|
|
|
bIsLMB_Pressed = false;
|
|
bIsRMB_Pressed = false;
|
|
|
|
bIsSpaceBarKeyPressed = false;
|
|
bIsDragging = false;
|
|
|
|
bAutoScroll = Settings.IsAutoScrollEnabled();
|
|
AutoScrollFrameAlignment = Settings.GetAutoScrollFrameAlignment();
|
|
AutoScrollViewportOffsetPercent = Settings.GetAutoScrollViewportOffsetPercent();
|
|
AutoScrollMinDelay = Settings.GetAutoScrollMinDelay();
|
|
LastAutoScrollTime = 0;
|
|
|
|
bIsPanning = false;
|
|
bAllowPanningOnScreenEdges = Settings.IsPanningOnScreenEdgesEnabled();
|
|
DPIScaleFactor = 1.0f;
|
|
EdgeFrameCountX = 0;
|
|
EdgeFrameCountY = 0;
|
|
PanningMode = EPanningMode::None;
|
|
|
|
OverscrollLeft = 0.0f;
|
|
OverscrollRight = 0.0f;
|
|
OverscrollTop = 0.0f;
|
|
OverscrollBottom = 0.0f;
|
|
|
|
bIsSelecting = false;
|
|
SelectionStartTime = 0.0;
|
|
SelectionEndTime = 0.0;
|
|
RaiseSelectionChanged();
|
|
|
|
if (HoveredTrack.IsValid())
|
|
{
|
|
HoveredTrack.Reset();
|
|
OnHoveredTrackChangedDelegate.Broadcast(HoveredTrack);
|
|
}
|
|
if (HoveredEvent.IsValid())
|
|
{
|
|
HoveredEvent.Reset();
|
|
OnHoveredEventChangedDelegate.Broadcast(HoveredEvent);
|
|
}
|
|
|
|
if (SelectedTrack.IsValid())
|
|
{
|
|
SelectedTrack.Reset();
|
|
OnSelectedTrackChangedDelegate.Broadcast(SelectedTrack);
|
|
}
|
|
if (SelectedEvent.IsValid())
|
|
{
|
|
SelectedEvent.Reset();
|
|
OnSelectedEventChangedDelegate.Broadcast(SelectedEvent);
|
|
}
|
|
|
|
if (TimingEventFilter.IsValid())
|
|
{
|
|
TimingEventFilter.Reset();
|
|
}
|
|
|
|
bPreventThrottling = false;
|
|
|
|
Tooltip.Reset();
|
|
|
|
LastSelectionType = ESelectionType::None;
|
|
|
|
//ThisGeometry
|
|
|
|
bDrawTopSeparatorLine = false;
|
|
bDrawBottomSeparatorLine = false;
|
|
|
|
//////////////////////////////////////////////////
|
|
|
|
NumUpdatedEvents = 0;
|
|
PreUpdateTracksDurationHistory.Reset();
|
|
PreUpdateTracksDurationHistory.AddValue(0);
|
|
UpdateTracksDurationHistory.Reset();
|
|
UpdateTracksDurationHistory.AddValue(0);
|
|
PostUpdateTracksDurationHistory.Reset();
|
|
PostUpdateTracksDurationHistory.AddValue(0);
|
|
TickDurationHistory.Reset();
|
|
TickDurationHistory.AddValue(0);
|
|
PreDrawTracksDurationHistory.Reset();
|
|
PreDrawTracksDurationHistory.AddValue(0);
|
|
DrawTracksDurationHistory.Reset();
|
|
DrawTracksDurationHistory.AddValue(0);
|
|
PostDrawTracksDurationHistory.Reset();
|
|
PostDrawTracksDurationHistory.AddValue(0);
|
|
OnPaintDeltaTimeHistory.Reset();
|
|
OnPaintDeltaTimeHistory.AddValue(0);
|
|
LastOnPaintTime = FPlatformTime::Cycles64();
|
|
|
|
//////////////////////////////////////////////////
|
|
|
|
for (Timing::ITimingViewExtender* Extender : GetExtenders())
|
|
{
|
|
Extender->OnBeginSession(*this);
|
|
}
|
|
#if UE_INSIGHTS_BACKWARD_COMPATIBILITY_UE54
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
::Insights::ITimingViewSession* CurrentTimingViewSession = (::Insights::ITimingViewSession*)(Timing::ITimingViewSession*)this;
|
|
for (::Insights::ITimingViewExtender* Extender : GetOldExtenders())
|
|
{
|
|
Extender->OnBeginSession(*CurrentTimingViewSession);
|
|
}
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
#endif // UE_INSIGHTS_BACKWARD_COMPATIBILITY_UE54
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool STimingView::IsOldGpu1TrackVisible() const
|
|
{
|
|
return ThreadTimingSharedState && ThreadTimingSharedState->IsOldGpu1TrackVisible();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool STimingView::IsOldGpu2TrackVisible() const
|
|
{
|
|
return ThreadTimingSharedState && ThreadTimingSharedState->IsOldGpu2TrackVisible();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool STimingView::IsAnyGpuTrackVisible() const
|
|
{
|
|
return ThreadTimingSharedState && ThreadTimingSharedState->IsAnyGpuTrackVisible();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool STimingView::IsGpuTrackVisible(uint32 InQueueId) const
|
|
{
|
|
return ThreadTimingSharedState && ThreadTimingSharedState->IsGpuTrackVisible(InQueueId);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool STimingView::IsCpuTrackVisible(uint32 InThreadId) const
|
|
{
|
|
return ThreadTimingSharedState && ThreadTimingSharedState->IsCpuTrackVisible(InThreadId);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
|
|
{
|
|
//SCompoundWidget::Tick(AllottedGeometry, InCurrentTime, InDeltaTime);
|
|
|
|
FStopwatch TickStopwatch;
|
|
TickStopwatch.Start();
|
|
|
|
LLM_SCOPE_BYTAG(Insights);
|
|
|
|
UpdateFilters();
|
|
|
|
ThisGeometry = AllottedGeometry;
|
|
|
|
Tooltip.SetFontScale(AllottedGeometry.Scale);
|
|
|
|
bPreventThrottling = false;
|
|
|
|
constexpr float OverscrollFadeSpeed = 2.0f;
|
|
if (OverscrollLeft > 0.0f)
|
|
{
|
|
OverscrollLeft = FMath::Max(0.0f, OverscrollLeft - InDeltaTime * OverscrollFadeSpeed);
|
|
}
|
|
if (OverscrollRight > 0.0f)
|
|
{
|
|
OverscrollRight = FMath::Max(0.0f, OverscrollRight - InDeltaTime * OverscrollFadeSpeed);
|
|
}
|
|
if (OverscrollTop > 0.0f)
|
|
{
|
|
OverscrollTop = FMath::Max(0.0f, OverscrollTop - InDeltaTime * OverscrollFadeSpeed);
|
|
}
|
|
if (OverscrollBottom > 0.0f)
|
|
{
|
|
OverscrollBottom = FMath::Max(0.0f, OverscrollBottom - InDeltaTime * OverscrollFadeSpeed);
|
|
}
|
|
|
|
const float ViewWidth = static_cast<float>(AllottedGeometry.GetLocalSize().X);
|
|
const float ViewHeight = static_cast<float>(AllottedGeometry.GetLocalSize().Y);
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Update viewport.
|
|
|
|
Viewport.SetPosY(32.0f); // height of toolbar
|
|
Viewport.UpdateSize(FMath::RoundToFloat(ViewWidth), FMath::RoundToFloat(ViewHeight) - Viewport.GetPosY());
|
|
|
|
if (!bIsPanning && !bAutoScroll)
|
|
{
|
|
// Elastic snap to horizontal time limits.
|
|
if (Viewport.EnforceHorizontalScrollLimits(0.5)) // 0.5 is the interpolation factor
|
|
{
|
|
UpdateHorizontalScrollBar();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Check the analysis session time.
|
|
|
|
TSharedPtr<const TraceServices::IAnalysisSession> Session = FInsightsManager::Get()->GetSession();
|
|
if (Session)
|
|
{
|
|
double SessionTime = 0.0;
|
|
{
|
|
TraceServices::FAnalysisSessionReadScope SessionReadScope(*Session.Get());
|
|
SessionTime = Session->GetDurationSeconds();
|
|
}
|
|
|
|
// Check if horizontal scroll area has changed.
|
|
if (SessionTime > Viewport.GetMaxValidTime() &&
|
|
SessionTime != DBL_MAX &&
|
|
SessionTime != std::numeric_limits<double>::infinity())
|
|
{
|
|
const double PreviousSessionTime = Viewport.GetMaxValidTime();
|
|
if ((PreviousSessionTime >= Viewport.GetStartTime() && PreviousSessionTime <= Viewport.GetEndTime()) ||
|
|
(SessionTime >= Viewport.GetStartTime() && SessionTime <= Viewport.GetEndTime()))
|
|
{
|
|
Viewport.AddDirtyFlags(ETimingTrackViewportDirtyFlags::HClippedSessionTimeChanged);
|
|
}
|
|
|
|
//UE_LOG(LogTimingProfiler, Log, TEXT("Session Duration: %g"), DT);
|
|
Viewport.SetMaxValidTime(SessionTime);
|
|
UpdateHorizontalScrollBar();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
if (bIsPanning)
|
|
{
|
|
// Disable auto-scroll if user starts panning manually.
|
|
SetAutoScroll(false);
|
|
}
|
|
|
|
if (bAutoScroll)
|
|
{
|
|
const uint64 CurrentTime = FPlatformTime::Cycles64();
|
|
if (static_cast<double>(CurrentTime - LastAutoScrollTime) * FPlatformTime::GetSecondsPerCycle64() > AutoScrollMinDelay)
|
|
{
|
|
const double ViewportDuration = Viewport.GetEndTime() - Viewport.GetStartTime(); // width of the viewport in [seconds]
|
|
const double AutoScrollViewportOffsetTime = ViewportDuration * AutoScrollViewportOffsetPercent;
|
|
|
|
// By default, align the current session time with the offseted right side of the viewport.
|
|
double MinStartTime = Viewport.GetMaxValidTime() - ViewportDuration + AutoScrollViewportOffsetTime;
|
|
|
|
if (AutoScrollFrameAlignment >= 0)
|
|
{
|
|
if (Session.IsValid())
|
|
{
|
|
TraceServices::FAnalysisSessionReadScope SessionReadScope(*Session.Get());
|
|
const TraceServices::IFrameProvider& FramesProvider = TraceServices::ReadFrameProvider(*Session.Get());
|
|
|
|
const ETraceFrameType FrameTpe = static_cast<ETraceFrameType>(AutoScrollFrameAlignment);
|
|
const uint64 FrameCount = FramesProvider.GetFrameCount(FrameTpe);
|
|
|
|
if (FrameCount > 0)
|
|
{
|
|
// Search the last frame with EndTime <= SessionTime.
|
|
uint64 FrameIndex = FrameCount;
|
|
while (FrameIndex > 0)
|
|
{
|
|
const TraceServices::FFrame* FramePtr = FramesProvider.GetFrame(FrameTpe, --FrameIndex);
|
|
if (FramePtr && FramePtr->EndTime <= Viewport.GetMaxValidTime())
|
|
{
|
|
// Align the start time of the frame with the right side of the viewport.
|
|
MinStartTime = FramePtr->EndTime - ViewportDuration + AutoScrollViewportOffsetTime;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Get the frame at the center of the viewport.
|
|
TraceServices::FFrame Frame;
|
|
const double ViewportCenter = MinStartTime + ViewportDuration / 2;
|
|
if (FramesProvider.GetFrameFromTime(FrameTpe, ViewportCenter, Frame))
|
|
{
|
|
if (Frame.EndTime > ViewportCenter)
|
|
{
|
|
// Align the start time of the frame with the center of the viewport.
|
|
MinStartTime = Frame.StartTime - ViewportDuration / 2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ScrollAtTime(MinStartTime);
|
|
LastAutoScrollTime = CurrentTime;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
if (Session)
|
|
{
|
|
// Tick plugin extenders.
|
|
// Each extender can add/remove tracks and/or change order of tracks.
|
|
for (Timing::ITimingViewExtender* Extender : GetExtenders())
|
|
{
|
|
Extender->Tick(*this, *Session.Get());
|
|
}
|
|
#if UE_INSIGHTS_BACKWARD_COMPATIBILITY_UE54
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
::Insights::ITimingViewSession* CurrentTimingViewSession = (::Insights::ITimingViewSession*)(Timing::ITimingViewSession*)this;
|
|
for (::Insights::ITimingViewExtender* Extender : GetOldExtenders())
|
|
{
|
|
Extender->Tick(*CurrentTimingViewSession, *Session.Get());
|
|
}
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
#endif // UE_INSIGHTS_BACKWARD_COMPATIBILITY_UE54
|
|
|
|
// Re-sort now (if we need to).
|
|
UpdateScrollableTracksOrder();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Animate the (vertical) layout transition (i.e. compact mode <-> normal mode).
|
|
Viewport.UpdateLayout();
|
|
|
|
TimeRulerTrack->SetSelection(bIsSelecting, SelectionStartTime, SelectionEndTime);
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
class FTimingTrackUpdateContext : public ITimingTrackUpdateContext
|
|
{
|
|
public:
|
|
explicit FTimingTrackUpdateContext(STimingView* InTimingView, const FGeometry& InGeometry, double InCurrentTime, float InDeltaTime)
|
|
: TimingView(InTimingView)
|
|
, Geometry(InGeometry)
|
|
, CurrentTime(InCurrentTime)
|
|
, DeltaTime(InDeltaTime)
|
|
{}
|
|
|
|
virtual const FGeometry& GetGeometry() const override { return Geometry; }
|
|
virtual const FTimingTrackViewport& GetViewport() const override { return TimingView->GetViewport(); }
|
|
virtual const FVector2D& GetMousePosition() const override { return TimingView->GetMousePosition(); }
|
|
virtual const TSharedPtr<const ITimingEvent> GetHoveredEvent() const override { return TimingView->GetHoveredEvent(); }
|
|
virtual const TSharedPtr<const ITimingEvent> GetSelectedEvent() const override { return TimingView->GetSelectedEvent(); }
|
|
virtual const TSharedPtr<ITimingEventFilter> GetEventFilter() const override { return TimingView->GetEventFilter(); }
|
|
virtual const TArray<TUniquePtr<ITimingEventRelation>>& GetCurrentRelations() const override { return TimingView->GetCurrentRelations(); }
|
|
virtual double GetCurrentTime() const override { return CurrentTime; }
|
|
virtual float GetDeltaTime() const override { return DeltaTime; }
|
|
|
|
public:
|
|
STimingView* TimingView;
|
|
const FGeometry& Geometry;
|
|
double CurrentTime;
|
|
float DeltaTime;
|
|
};
|
|
|
|
FTimingTrackUpdateContext UpdateContext(this, AllottedGeometry, InCurrentTime, InDeltaTime);
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Pre-Update.
|
|
// The tracks needs to update their size.
|
|
|
|
{
|
|
FStopwatch PreUpdateTracksStopwatch;
|
|
PreUpdateTracksStopwatch.Start();
|
|
|
|
for (TSharedPtr<FBaseTimingTrack>& TrackPtr : TopDockedTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
TrackPtr->PreUpdate(UpdateContext);
|
|
}
|
|
}
|
|
|
|
for (TSharedPtr<FBaseTimingTrack>& TrackPtr : BottomDockedTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
TrackPtr->PreUpdate(UpdateContext);
|
|
}
|
|
}
|
|
|
|
for (TSharedPtr<FBaseTimingTrack>& TrackPtr : ScrollableTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
TrackPtr->PreUpdate(UpdateContext);
|
|
}
|
|
}
|
|
|
|
for (TSharedPtr<FBaseTimingTrack>& TrackPtr : ForegroundTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
TrackPtr->PreUpdate(UpdateContext);
|
|
}
|
|
}
|
|
|
|
PreUpdateTracksStopwatch.Stop();
|
|
PreUpdateTracksDurationHistory.AddValue(PreUpdateTracksStopwatch.AccumulatedTime);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Update Y position for the visible top docked tracks.
|
|
// Compute the total height of top docked areas.
|
|
|
|
int32 NumVisibleTopDockedTracks = 0;
|
|
float TopOffset = 0.0f;
|
|
for (TSharedPtr<FBaseTimingTrack>& TrackPtr : TopDockedTracks)
|
|
{
|
|
SetTrackPosY(*TrackPtr, Viewport.GetPosY() + TopOffset);
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
TopOffset += TrackPtr->GetHeight();
|
|
NumVisibleTopDockedTracks++;
|
|
}
|
|
}
|
|
if (NumVisibleTopDockedTracks > 0)
|
|
{
|
|
bDrawTopSeparatorLine = true;
|
|
TopOffset += 2.0f;
|
|
}
|
|
else
|
|
{
|
|
bDrawTopSeparatorLine = false;
|
|
}
|
|
Viewport.SetTopOffset(TopOffset);
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Update Y position for the visible bottom docked tracks.
|
|
// Compute the total height of bottom docked areas.
|
|
|
|
float BottomOffset = 0.0f;
|
|
int32 NumVisibleBottomDockedTracks = 0;
|
|
for (TSharedPtr<FBaseTimingTrack>& TrackPtr : BottomDockedTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
BottomOffset += TrackPtr->GetHeight();
|
|
NumVisibleBottomDockedTracks++;
|
|
}
|
|
}
|
|
if (NumVisibleBottomDockedTracks > 0)
|
|
{
|
|
BottomOffset += 2.0f;
|
|
if (Viewport.GetTopOffset() + BottomOffset > Viewport.GetHeight())
|
|
{
|
|
BottomOffset = Viewport.GetHeight() - Viewport.GetTopOffset();
|
|
}
|
|
float BottomDockedTrackPosY = Viewport.GetPosY() + Viewport.GetHeight() - BottomOffset + 2.0f;
|
|
for (TSharedPtr<FBaseTimingTrack>& TrackPtr : BottomDockedTracks)
|
|
{
|
|
SetTrackPosY(*TrackPtr, BottomDockedTrackPosY);
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
BottomDockedTrackPosY += TrackPtr->GetHeight();
|
|
}
|
|
}
|
|
}
|
|
bDrawBottomSeparatorLine = (BottomOffset > 0.0f);
|
|
Viewport.SetBottomOffset(BottomOffset);
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Compute the total height of visible scrollable tracks.
|
|
|
|
float ScrollHeight = 0.0f;
|
|
for (TSharedPtr<FBaseTimingTrack>& TrackPtr : ScrollableTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
ScrollHeight += TrackPtr->GetHeight();
|
|
}
|
|
}
|
|
ScrollHeight += 1.0f; // allow 1 pixel at the bottom (for last horizontal line)
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Check if vertical scroll area has changed.
|
|
|
|
bool bScrollHeightChanged = false;
|
|
if (ScrollHeight != Viewport.GetScrollHeight())
|
|
{
|
|
bScrollHeightChanged = true;
|
|
Viewport.SetScrollHeight(ScrollHeight);
|
|
UpdateVerticalScrollBar();
|
|
}
|
|
|
|
// Set the VerticalScrollBar padding so it is limited to the scrollable area.
|
|
VerticalScrollBar->SetPadding(FMargin(0.0f, Viewport.GetPosY() + TopOffset + 2.0f, 0.0f, FMath::Max(BottomOffset + 2.0f, 10.0f)));
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
const float InitialScrollPosY = Viewport.GetScrollPosY();
|
|
|
|
TSharedPtr<FBaseTimingTrack> SelectedScrollableTrack;
|
|
if (SelectedTrack.IsValid() && SelectedTrack->IsVisible())
|
|
{
|
|
if (ScrollableTracks.Contains(SelectedTrack))
|
|
{
|
|
SelectedScrollableTrack = SelectedTrack;
|
|
}
|
|
}
|
|
|
|
const float InitialPinnedTrackPosY = SelectedScrollableTrack.IsValid() ? SelectedScrollableTrack->GetPosY() : 0.0f;
|
|
|
|
// Update the Y position for visible scrollable tracks.
|
|
UpdatePositionForScrollableTracks();
|
|
|
|
// The selected track will be pinned (keeps Y pos fixed unless user scrolls vertically).
|
|
if (SelectedScrollableTrack.IsValid())
|
|
{
|
|
const float ScrollingDY = LastScrollPosY - InitialScrollPosY;
|
|
const float PinnedTrackPosY = SelectedScrollableTrack->GetPosY();
|
|
const float AdjustmentDY = InitialPinnedTrackPosY - PinnedTrackPosY + ScrollingDY;
|
|
|
|
if (!FMath::IsNearlyZero(AdjustmentDY, 0.5f))
|
|
{
|
|
ViewportScrollPosYOnButtonDown -= AdjustmentDY;
|
|
ScrollAtPosY(InitialScrollPosY - AdjustmentDY);
|
|
UpdatePositionForScrollableTracks();
|
|
}
|
|
}
|
|
|
|
// Elastic snap to vertical scroll limits.
|
|
if (!bIsPanning)
|
|
{
|
|
const float DY = Viewport.GetScrollHeight() - Viewport.GetScrollableAreaHeight();
|
|
const float MinY = FMath::Min(DY, 0.0f);
|
|
const float MaxY = DY - MinY;
|
|
|
|
float ScrollPosY = Viewport.GetScrollPosY();
|
|
|
|
if (ScrollPosY < MinY)
|
|
{
|
|
if (bScrollHeightChanged || Viewport.IsDirty(ETimingTrackViewportDirtyFlags::VLayoutChanged))
|
|
{
|
|
ScrollPosY = MinY;
|
|
}
|
|
else
|
|
{
|
|
constexpr float U = 0.5f;
|
|
ScrollPosY = ScrollPosY * U + (1.0f - U) * MinY;
|
|
if (FMath::IsNearlyEqual(ScrollPosY, MinY, 0.5f))
|
|
{
|
|
ScrollPosY = MinY;
|
|
}
|
|
}
|
|
}
|
|
else if (ScrollPosY > MaxY)
|
|
{
|
|
if (bScrollHeightChanged || Viewport.IsDirty(ETimingTrackViewportDirtyFlags::VLayoutChanged))
|
|
{
|
|
ScrollPosY = MaxY;
|
|
}
|
|
else
|
|
{
|
|
constexpr float U = 0.5f;
|
|
ScrollPosY = ScrollPosY * U + (1.0f - U) * MaxY;
|
|
if (FMath::IsNearlyEqual(ScrollPosY, MaxY, 0.5f))
|
|
{
|
|
ScrollPosY = MaxY;
|
|
}
|
|
}
|
|
if (ScrollPosY < MinY)
|
|
{
|
|
ScrollPosY = MinY;
|
|
}
|
|
}
|
|
|
|
if (ScrollPosY != Viewport.GetScrollPosY())
|
|
{
|
|
ScrollAtPosY(ScrollPosY);
|
|
UpdatePositionForScrollableTracks();
|
|
}
|
|
}
|
|
|
|
LastScrollPosY = Viewport.GetScrollPosY();
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// At this point it is assumed all tracks have proper position and size.
|
|
// Update.
|
|
{
|
|
FStopwatch UpdateTracksStopwatch;
|
|
UpdateTracksStopwatch.Start();
|
|
|
|
for (TSharedPtr<FBaseTimingTrack>& TrackPtr : TopDockedTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
TrackPtr->Update(UpdateContext);
|
|
}
|
|
}
|
|
|
|
for (TSharedPtr<FBaseTimingTrack>& TrackPtr : BottomDockedTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
TrackPtr->Update(UpdateContext);
|
|
}
|
|
}
|
|
|
|
for (TSharedPtr<FBaseTimingTrack>& TrackPtr : ScrollableTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
TrackPtr->Update(UpdateContext);
|
|
}
|
|
}
|
|
|
|
for (TSharedPtr<FBaseTimingTrack>& TrackPtr : ForegroundTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
TrackPtr->Update(UpdateContext);
|
|
}
|
|
}
|
|
|
|
UpdateTracksStopwatch.Stop();
|
|
UpdateTracksDurationHistory.AddValue(UpdateTracksStopwatch.AccumulatedTime);
|
|
}
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Post-Update.
|
|
{
|
|
FStopwatch PostUpdateTracksStopwatch;
|
|
PostUpdateTracksStopwatch.Start();
|
|
|
|
for (TSharedPtr<FBaseTimingTrack>& TrackPtr : TopDockedTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
TrackPtr->PostUpdate(UpdateContext);
|
|
}
|
|
}
|
|
|
|
for (TSharedPtr<FBaseTimingTrack>& TrackPtr : BottomDockedTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
TrackPtr->PostUpdate(UpdateContext);
|
|
}
|
|
}
|
|
|
|
for (TSharedPtr<FBaseTimingTrack>& TrackPtr : ScrollableTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
TrackPtr->PostUpdate(UpdateContext);
|
|
}
|
|
}
|
|
|
|
for (TSharedPtr<FBaseTimingTrack>& TrackPtr : ForegroundTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
TrackPtr->PostUpdate(UpdateContext);
|
|
}
|
|
}
|
|
|
|
PostUpdateTracksStopwatch.Stop();
|
|
PostUpdateTracksDurationHistory.AddValue(PostUpdateTracksStopwatch.AccumulatedTime);
|
|
}
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
Tooltip.Update();
|
|
if (!MousePosition.IsZero())
|
|
{
|
|
Tooltip.SetPosition(MousePosition, 0.0f, Viewport.GetWidth() - 12.0f, Viewport.GetPosY(), Viewport.GetPosY() + Viewport.GetHeight() - 12.0f); // -12.0f is to avoid overlapping the scrollbars
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Update "hovered" and "selected" flags for all visible tracks.
|
|
//TODO: Move this before PreUpdate (so a track could adjust its size based on hovered/selected flags).
|
|
|
|
// Reset hovered/selected flags for all tracks.
|
|
for (TSharedPtr<FBaseTimingTrack>& TrackPtr : TopDockedTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
TrackPtr->SetHoveredState(false);
|
|
TrackPtr->SetSelectedFlag(false);
|
|
}
|
|
}
|
|
for (TSharedPtr<FBaseTimingTrack>& TrackPtr : BottomDockedTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
TrackPtr->SetHoveredState(false);
|
|
TrackPtr->SetSelectedFlag(false);
|
|
}
|
|
}
|
|
for (TSharedPtr<FBaseTimingTrack>& TrackPtr : ScrollableTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
TrackPtr->SetHoveredState(false);
|
|
TrackPtr->SetSelectedFlag(false);
|
|
}
|
|
}
|
|
for (TSharedPtr<FBaseTimingTrack>& TrackPtr : ForegroundTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
TrackPtr->SetHoveredState(false);
|
|
TrackPtr->SetSelectedFlag(false);
|
|
}
|
|
}
|
|
|
|
// Set the hovered flag for the actual hovered track, if any.
|
|
if (HoveredTrack.IsValid())
|
|
{
|
|
HoveredTrack->SetHoveredState(true);
|
|
}
|
|
|
|
// Set the selected flag for the actual selected track, if any.
|
|
if (SelectedTrack.IsValid())
|
|
{
|
|
SelectedTrack->SetSelectedFlag(true);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
Viewport.ResetDirtyFlags();
|
|
|
|
if (bBringSelectedEventIntoViewVerticallyOnNextTick && SelectedEvent.IsValid())
|
|
{
|
|
if (SelectedEvent->GetTrack()->GetLocation() == ETimingTrackLocation::Scrollable)
|
|
{
|
|
BringScrollableTrackIntoView(*SelectedEvent->GetTrack());
|
|
}
|
|
}
|
|
|
|
bBringSelectedEventIntoViewVerticallyOnNextTick = false;
|
|
|
|
TickStopwatch.Stop();
|
|
TickDurationHistory.AddValue(TickStopwatch.AccumulatedTime);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::UpdatePositionForScrollableTracks()
|
|
{
|
|
// Update the Y position for the visible scrollable tracks.
|
|
float ScrollableTrackPosY = Viewport.GetPosY() + Viewport.GetTopOffset() - Viewport.GetScrollPosY();
|
|
for (TSharedPtr<FBaseTimingTrack>& TrackPtr : ScrollableTracks)
|
|
{
|
|
SetTrackPosY(*TrackPtr, ScrollableTrackPosY);
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
ScrollableTrackPosY += TrackPtr->GetHeight();
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::SetTrackPosY(FBaseTimingTrack& Track, float TrackPosY) const
|
|
{
|
|
Track.SetPosY(TrackPosY);
|
|
Track.UpdateChildTracksPosY(Viewport.GetLayout());
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int32 STimingView::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
|
|
{
|
|
FStopwatch Stopwatch;
|
|
Stopwatch.Start();
|
|
|
|
const bool bEnabled = ShouldBeEnabled(bParentEnabled);
|
|
const ESlateDrawEffect DrawEffects = bEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect;
|
|
FDrawContext DrawContext(AllottedGeometry, MyCullingRect, InWidgetStyle, DrawEffects, OutDrawElements, LayerId);
|
|
|
|
#if 0 // Enabling this may further increase UI performance (TODO: profile if this is really needed again).
|
|
// Avoids multiple resizes of Slate's draw elements buffers.
|
|
OutDrawElements.GetRootDrawLayer().DrawElements.Reserve(50000);
|
|
#endif
|
|
|
|
const TSharedRef<FSlateFontMeasure> FontMeasureService = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
|
|
const float FontScale = AllottedGeometry.Scale;
|
|
|
|
const float ViewWidth = static_cast<float>(AllottedGeometry.GetLocalSize().X);
|
|
const float ViewHeight = static_cast<float>(AllottedGeometry.GetLocalSize().Y);
|
|
|
|
#if 0 // Enabling this may further increase UI performance (TODO: profile if this is really needed again).
|
|
// Warm up Slate vertex/index buffers to avoid initial freezes due to multiple resizes of those buffers.
|
|
static bool bWarmingUp = false;
|
|
if (!bWarmingUp)
|
|
{
|
|
bWarmingUp = true;
|
|
|
|
FRandomStream RandomStream(0);
|
|
const int32 Count = 1'000'000;
|
|
for (int32 Index = 0; Index < Count; ++Index)
|
|
{
|
|
float X = ViewWidth * RandomStream.GetFraction();
|
|
float Y = ViewHeight * RandomStream.GetFraction();
|
|
FLinearColor Color(RandomStream.GetFraction(), RandomStream.GetFraction(), RandomStream.GetFraction(), 1.0f);
|
|
DrawContext.DrawBox(X, Y, 1.0f, 1.0f, WhiteBrush, Color);
|
|
}
|
|
LayerId++;
|
|
LayerId++;
|
|
}
|
|
#endif
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
class FTimingTrackDrawContext : public ITimingTrackDrawContext
|
|
{
|
|
public:
|
|
explicit FTimingTrackDrawContext(const STimingView* InTimingView, FDrawContext& InDrawContext, const FTimingViewDrawHelper& InHelper)
|
|
: TimingView(InTimingView)
|
|
, DrawContext(InDrawContext)
|
|
, Helper(InHelper)
|
|
{}
|
|
|
|
virtual const FTimingTrackViewport& GetViewport() const override { return TimingView->GetViewport(); }
|
|
virtual const FVector2D& GetMousePosition() const override { return TimingView->GetMousePosition(); }
|
|
virtual const TSharedPtr<const ITimingEvent> GetHoveredEvent() const override { return TimingView->GetHoveredEvent(); }
|
|
virtual const TSharedPtr<const ITimingEvent> GetSelectedEvent() const override { return TimingView->GetSelectedEvent(); }
|
|
virtual const TSharedPtr<ITimingEventFilter> GetEventFilter() const override { return TimingView->GetEventFilter(); }
|
|
virtual FDrawContext& GetDrawContext() const override { return DrawContext; }
|
|
virtual const ITimingViewDrawHelper& GetHelper() const override { return Helper; }
|
|
|
|
public:
|
|
const STimingView* TimingView;
|
|
FDrawContext& DrawContext;
|
|
const FTimingViewDrawHelper& Helper;
|
|
};
|
|
|
|
FTimingViewDrawHelper Helper(DrawContext, Viewport);
|
|
FTimingTrackDrawContext TimingDrawContext(this, DrawContext, Helper);
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Draw background.
|
|
Helper.DrawBackground();
|
|
|
|
//////////////////////////////////////////////////
|
|
// Pre-Draw
|
|
{
|
|
FStopwatch PreDrawTracksStopwatch;
|
|
PreDrawTracksStopwatch.Start();
|
|
|
|
for (const TSharedPtr<FBaseTimingTrack>& TrackPtr : ScrollableTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
TrackPtr->PreDraw(TimingDrawContext);
|
|
}
|
|
}
|
|
|
|
for (const TSharedPtr<FBaseTimingTrack>& TrackPtr : TopDockedTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
TrackPtr->PreDraw(TimingDrawContext);
|
|
}
|
|
}
|
|
|
|
for (const TSharedPtr<FBaseTimingTrack>& TrackPtr : BottomDockedTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
TrackPtr->PreDraw(TimingDrawContext);
|
|
}
|
|
}
|
|
|
|
for (const TSharedPtr<FBaseTimingTrack>& TrackPtr : ForegroundTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
TrackPtr->PreDraw(TimingDrawContext);
|
|
}
|
|
}
|
|
|
|
PreDrawTracksStopwatch.Stop();
|
|
PreDrawTracksDurationHistory.AddValue(PreDrawTracksStopwatch.AccumulatedTime);
|
|
}
|
|
|
|
const FVector2f Position = FVector2f(AllottedGeometry.GetAbsolutePosition());
|
|
const float Scale = AllottedGeometry.GetAccumulatedLayoutTransform().GetScale();
|
|
|
|
//////////////////////////////////////////////////
|
|
// Draw
|
|
{
|
|
FStopwatch DrawTracksStopwatch;
|
|
DrawTracksStopwatch.Start();
|
|
|
|
Helper.BeginDrawTracks();
|
|
|
|
// Draw the scrollable tracks.
|
|
{
|
|
const float TopY = Viewport.GetPosY() + Viewport.GetTopOffset();
|
|
const float BottomY = Viewport.GetPosY() + Viewport.GetHeight() - Viewport.GetBottomOffset();
|
|
|
|
{
|
|
const float L = Position.X;
|
|
const float R = Position.X + Viewport.GetWidth() * Scale;
|
|
const float T = Position.Y + TopY * Scale;
|
|
const float B = Position.Y + BottomY * Scale;
|
|
const FSlateClippingZone ClipZone(FSlateRect(L, T, R, B));
|
|
DrawContext.ElementList.PushClip(ClipZone);
|
|
}
|
|
|
|
for (const TSharedPtr<FBaseTimingTrack>& TrackPtr : ScrollableTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
if (TrackPtr->GetPosY() + TrackPtr->GetHeight() <= TopY)
|
|
{
|
|
continue;
|
|
}
|
|
if (TrackPtr->GetPosY() >= BottomY)
|
|
{
|
|
break;
|
|
}
|
|
TrackPtr->Draw(TimingDrawContext);
|
|
}
|
|
}
|
|
|
|
// Draw relations between scrollable tracks.
|
|
const FTimingViewDrawHelper& TimingHelper = *static_cast<const FTimingViewDrawHelper*>(&TimingDrawContext.GetHelper());
|
|
TimingHelper.DrawRelations(CurrentRelations, ITimingEventRelation::EDrawFilter::BetweenScrollableTracks);
|
|
|
|
DrawContext.ElementList.PopClip();
|
|
}
|
|
|
|
// Draw the top docked tracks.
|
|
{
|
|
const float TopY = Viewport.GetPosY();
|
|
const float BottomY = Viewport.GetPosY() + Viewport.GetTopOffset();
|
|
|
|
{
|
|
const float L = Position.X;
|
|
const float R = Position.X + Viewport.GetWidth() * Scale;
|
|
const float T = Position.Y + TopY * Scale;
|
|
const float B = Position.Y + BottomY * Scale;
|
|
const FSlateClippingZone ClipZone(FSlateRect(L, T, R, B));
|
|
DrawContext.ElementList.PushClip(ClipZone);
|
|
}
|
|
|
|
for (const TSharedPtr<FBaseTimingTrack>& TrackPtr : TopDockedTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
TrackPtr->Draw(TimingDrawContext);
|
|
}
|
|
}
|
|
|
|
if (bDrawTopSeparatorLine)
|
|
{
|
|
// Draw separator line between top docked tracks and scrollable tracks.
|
|
DrawContext.DrawBox(0.0f, BottomY - 2.0f, Viewport.GetWidth(), 2.0f, WhiteBrush, FLinearColor(0.01f, 0.01f, 0.01f, 1.0f));
|
|
++DrawContext.LayerId;
|
|
}
|
|
|
|
DrawContext.ElementList.PopClip();
|
|
}
|
|
|
|
// Draw the bottom docked tracks.
|
|
{
|
|
const float TopY = Viewport.GetPosY() + Viewport.GetHeight() - Viewport.GetBottomOffset();
|
|
const float BottomY = Viewport.GetPosY() + Viewport.GetHeight();
|
|
|
|
{
|
|
const float L = Position.X;
|
|
const float R = Position.X + Viewport.GetWidth() * Scale;
|
|
const float T = Position.Y + TopY * Scale;
|
|
const float B = Position.Y + BottomY * Scale;
|
|
const FSlateClippingZone ClipZone(FSlateRect(L, T, R, B));
|
|
DrawContext.ElementList.PushClip(ClipZone);
|
|
}
|
|
|
|
for (const TSharedPtr<FBaseTimingTrack>& TrackPtr : BottomDockedTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
TrackPtr->Draw(TimingDrawContext);
|
|
}
|
|
}
|
|
|
|
if (bDrawBottomSeparatorLine)
|
|
{
|
|
// Draw separator line between top docked tracks and scrollable tracks.
|
|
DrawContext.DrawBox(0.0f, TopY, Viewport.GetWidth(), 2.0f, WhiteBrush, FLinearColor(0.01f, 0.01f, 0.01f, 1.0f));
|
|
++DrawContext.LayerId;
|
|
}
|
|
|
|
DrawContext.ElementList.PopClip();
|
|
}
|
|
|
|
// Draw the foreground tracks.
|
|
for (const TSharedPtr<FBaseTimingTrack>& TrackPtr : ForegroundTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
TrackPtr->Draw(TimingDrawContext);
|
|
}
|
|
}
|
|
|
|
Helper.EndDrawTracks();
|
|
|
|
DrawTracksStopwatch.Stop();
|
|
DrawTracksDurationHistory.AddValue(DrawTracksStopwatch.AccumulatedTime);
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
// Draw the selected and/or hovered event.
|
|
|
|
if (ITimingEvent::AreValidAndEquals(SelectedEvent, HoveredEvent))
|
|
{
|
|
const TSharedRef<const FBaseTimingTrack> TrackPtr = SelectedEvent->GetTrack();
|
|
|
|
// Highlight the selected and hovered timing event (if any).
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
SelectedEvent->GetTrack()->DrawEvent(TimingDrawContext, *SelectedEvent, EDrawEventMode::SelectedAndHovered);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Highlight the selected timing event (if any).
|
|
if (SelectedEvent.IsValid())
|
|
{
|
|
const TSharedRef<const FBaseTimingTrack> TrackPtr = SelectedEvent->GetTrack();
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
SelectedEvent->GetTrack()->DrawEvent(TimingDrawContext, *SelectedEvent, EDrawEventMode::Selected);
|
|
}
|
|
}
|
|
|
|
// Highlight the hovered timing event (if any).
|
|
if (HoveredEvent.IsValid())
|
|
{
|
|
const TSharedRef<const FBaseTimingTrack> TrackPtr = HoveredEvent->GetTrack();
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
HoveredEvent->GetTrack()->DrawEvent(TimingDrawContext, *HoveredEvent, EDrawEventMode::Hovered);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Draw the time range selection.
|
|
FDrawHelpers::DrawTimeRangeSelection(DrawContext, Viewport, SelectionStartTime, SelectionEndTime, WhiteBrush, MainFont);
|
|
|
|
//////////////////////////////////////////////////
|
|
// Post-Draw
|
|
{
|
|
FStopwatch PostDrawTracksStopwatch;
|
|
PostDrawTracksStopwatch.Start();
|
|
|
|
for (const TSharedPtr<FBaseTimingTrack>& TrackPtr : ScrollableTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
TrackPtr->PostDraw(TimingDrawContext);
|
|
}
|
|
}
|
|
|
|
for (const TSharedPtr<FBaseTimingTrack>& TrackPtr : TopDockedTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
TrackPtr->PostDraw(TimingDrawContext);
|
|
}
|
|
}
|
|
|
|
for (const TSharedPtr<FBaseTimingTrack>& TrackPtr : BottomDockedTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
TrackPtr->PostDraw(TimingDrawContext);
|
|
}
|
|
}
|
|
|
|
for (const TSharedPtr<FBaseTimingTrack>& TrackPtr : ForegroundTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
TrackPtr->PostDraw(TimingDrawContext);
|
|
}
|
|
}
|
|
|
|
PostDrawTracksStopwatch.Stop();
|
|
PostDrawTracksDurationHistory.AddValue(PostDrawTracksStopwatch.AccumulatedTime);
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
// Draw relations between docked tracks.
|
|
{
|
|
bool bIsClipZoneSet = false;
|
|
if (GraphTrack->IsVisible() &&
|
|
GraphTrack->GetLocation() == ETimingTrackLocation::TopDocked)
|
|
{
|
|
// Avoid overlapping the Main Graph track (when top docked).
|
|
const float TopY = GraphTrack->GetPosY() + GraphTrack->GetHeight();
|
|
const float BottomY = Viewport.GetPosY() + Viewport.GetHeight();
|
|
|
|
const float L = Position.X;
|
|
const float R = Position.X + Viewport.GetWidth() * Scale;
|
|
const float T = Position.Y + TopY * Scale;
|
|
const float B = Position.Y + BottomY * Scale;
|
|
const FSlateClippingZone ClipZone(FSlateRect(L, T, R, B));
|
|
DrawContext.ElementList.PushClip(ClipZone);
|
|
bIsClipZoneSet = true;
|
|
}
|
|
|
|
const FTimingViewDrawHelper& TimingHelper = *static_cast<const FTimingViewDrawHelper*>(&TimingDrawContext.GetHelper());
|
|
TimingHelper.DrawRelations(CurrentRelations, ITimingEventRelation::EDrawFilter::BetweenDockedTracks);
|
|
|
|
if (bIsClipZoneSet)
|
|
{
|
|
DrawContext.ElementList.PopClip();
|
|
}
|
|
}
|
|
//////////////////////////////////////////////////
|
|
|
|
// Draw tooltip with info about hovered event.
|
|
Tooltip.Draw(DrawContext);
|
|
|
|
// Fill the background of the toolbar.
|
|
DrawContext.DrawBox(0.0f, 0.0f, ViewWidth, Viewport.GetPosY(), WhiteBrush, FSlateColor(EStyleColor::Panel).GetSpecifiedColor());
|
|
|
|
// Fill the background of the vertical scrollbar.
|
|
const float ScrollBarHeight = Viewport.GetScrollableAreaHeight();
|
|
if (ScrollBarHeight > 0)
|
|
{
|
|
constexpr float ScrollBarWidth = 12.0f;
|
|
DrawContext.DrawBox(ViewWidth - ScrollBarWidth, Viewport.GetPosY() + Viewport.GetTopOffset(), ScrollBarWidth, ScrollBarHeight, WhiteBrush, FSlateColor(EStyleColor::Panel).GetSpecifiedColor());
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
// Draw the overscroll indication lines.
|
|
|
|
constexpr float OverscrollLineSize = 1.0f;
|
|
constexpr int32 OverscrollLineCount = 8;
|
|
|
|
if (OverscrollLeft > 0.0f)
|
|
{
|
|
// TODO: single box with gradient opacity
|
|
const float OverscrollLineY = Viewport.GetPosY();
|
|
const float OverscrollLineH = Viewport.GetHeight();
|
|
for (int32 LineIndex = 0; LineIndex < OverscrollLineCount; ++LineIndex)
|
|
{
|
|
const float Opacity = OverscrollLeft * static_cast<float>(OverscrollLineCount - LineIndex) / static_cast<float>(OverscrollLineCount);
|
|
DrawContext.DrawBox(static_cast<float>(LineIndex) * OverscrollLineSize, OverscrollLineY, OverscrollLineSize, OverscrollLineH, WhiteBrush, FLinearColor(1.0f, 0.1f, 0.1f, Opacity));
|
|
}
|
|
}
|
|
if (OverscrollRight > 0.0f)
|
|
{
|
|
const float OverscrollLineY = Viewport.GetPosY();
|
|
const float OverscrollLineH = Viewport.GetHeight();
|
|
for (int32 LineIndex = 0; LineIndex < OverscrollLineCount; ++LineIndex)
|
|
{
|
|
const float Opacity = OverscrollRight * static_cast<float>(OverscrollLineCount - LineIndex) / static_cast<float>(OverscrollLineCount);
|
|
DrawContext.DrawBox(ViewWidth - static_cast<float>(1 + LineIndex) * OverscrollLineSize, OverscrollLineY, OverscrollLineSize, OverscrollLineH, WhiteBrush, FLinearColor(1.0f, 0.1f, 0.1f, Opacity));
|
|
}
|
|
}
|
|
if (OverscrollTop > 0.0f)
|
|
{
|
|
const float OverscrollLineY = Viewport.GetPosY() + Viewport.GetTopOffset();
|
|
for (int32 LineIndex = 0; LineIndex < OverscrollLineCount; ++LineIndex)
|
|
{
|
|
const float Opacity = OverscrollTop * static_cast<float>(OverscrollLineCount - LineIndex) / static_cast<float>(OverscrollLineCount);
|
|
DrawContext.DrawBox(0.0f, OverscrollLineY + static_cast<float>(LineIndex) * OverscrollLineSize, ViewWidth, OverscrollLineSize, WhiteBrush, FLinearColor(1.0f, 0.1f, 0.1f, Opacity));
|
|
}
|
|
}
|
|
if (OverscrollBottom > 0.0f)
|
|
{
|
|
const float OverscrollLineY = Viewport.GetPosY() + Viewport.GetHeight() - Viewport.GetBottomOffset();
|
|
for (int32 LineIndex = 0; LineIndex < OverscrollLineCount; ++LineIndex)
|
|
{
|
|
const float Opacity = OverscrollBottom * static_cast<float>(OverscrollLineCount - LineIndex) / static_cast<float>(OverscrollLineCount);
|
|
DrawContext.DrawBox(0.0f, OverscrollLineY - static_cast<float>(1 + LineIndex) * OverscrollLineSize, ViewWidth, OverscrollLineSize, WhiteBrush, FLinearColor(1.0f, 0.1f, 0.1f, Opacity));
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
|
|
const bool bShouldDisplayDebugInfo = FInsightsManager::Get()->IsDebugInfoEnabled();
|
|
if (bShouldDisplayDebugInfo)
|
|
{
|
|
const FSlateFontInfo& SummaryFont = MainFont;
|
|
|
|
const float MaxFontCharHeight = static_cast<float>(FontMeasureService->Measure(TEXT("!"), SummaryFont, FontScale).Y / FontScale);
|
|
const float DbgDY = MaxFontCharHeight;
|
|
|
|
const float DbgW = 320.0f;
|
|
const float DbgH = DbgDY * 9.0f + 3.0f;
|
|
const float DbgX = ViewWidth - DbgW - 20.0f;
|
|
float DbgY = Viewport.GetPosY() + Viewport.GetTopOffset() + 10.0f;
|
|
|
|
DrawContext.LayerId++;
|
|
|
|
DrawContext.DrawBox(DbgX - 2.0f, DbgY - 2.0f, DbgW, DbgH, WhiteBrush, FLinearColor(1.0f, 1.0f, 1.0f, 0.9f));
|
|
DrawContext.LayerId++;
|
|
|
|
FLinearColor DbgTextColor(0.0f, 0.0f, 0.0f, 0.9f);
|
|
|
|
//////////////////////////////////////////////////
|
|
// Display the "Draw" performance info.
|
|
|
|
// Time interval since last OnPaint call.
|
|
const uint64 CurrentTime = FPlatformTime::Cycles64();
|
|
const uint64 OnPaintDeltaTime = CurrentTime - LastOnPaintTime;
|
|
LastOnPaintTime = CurrentTime;
|
|
OnPaintDeltaTimeHistory.AddValue(OnPaintDeltaTime); // saved for last 32 OnPaint calls
|
|
const uint64 AvgOnPaintDeltaTime = OnPaintDeltaTimeHistory.ComputeAverage();
|
|
const uint64 AvgOnPaintDeltaTimeMs = FStopwatch::Cycles64ToMilliseconds(AvgOnPaintDeltaTime);
|
|
const double AvgOnPaintFps = AvgOnPaintDeltaTimeMs != 0 ? 1.0 / FStopwatch::Cycles64ToSeconds(AvgOnPaintDeltaTime) : 0.0;
|
|
|
|
const uint64 AvgPreDrawTracksDurationMs = FStopwatch::Cycles64ToMilliseconds(PreDrawTracksDurationHistory.ComputeAverage());
|
|
const uint64 AvgDrawTracksDurationMs = FStopwatch::Cycles64ToMilliseconds(DrawTracksDurationHistory.ComputeAverage());
|
|
const uint64 AvgPostDrawTracksDurationMs = FStopwatch::Cycles64ToMilliseconds(PostDrawTracksDurationHistory.ComputeAverage());
|
|
const uint64 AvgTotalDrawDurationMs = FStopwatch::Cycles64ToMilliseconds(TotalDrawDurationHistory.ComputeAverage());
|
|
|
|
DrawContext.DrawText
|
|
(
|
|
DbgX, DbgY,
|
|
FString::Printf(TEXT("D: %" UINT64_FMT " ms + %" UINT64_FMT " ms + %" UINT64_FMT " ms + %" UINT64_FMT " ms = %" UINT64_FMT " ms | + %" UINT64_FMT " ms = %" UINT64_FMT " ms (%" INT64_FMT " fps)"),
|
|
AvgPreDrawTracksDurationMs, // pre-draw tracks time
|
|
AvgDrawTracksDurationMs, // draw tracks time
|
|
AvgPostDrawTracksDurationMs, // post-draw tracks time
|
|
AvgTotalDrawDurationMs - AvgPreDrawTracksDurationMs - AvgDrawTracksDurationMs - AvgPostDrawTracksDurationMs, // other draw code
|
|
AvgTotalDrawDurationMs,
|
|
AvgOnPaintDeltaTimeMs - AvgTotalDrawDurationMs, // other overhead to OnPaint calls
|
|
AvgOnPaintDeltaTimeMs, // average time between two OnPaint calls
|
|
FMath::RoundToInt(AvgOnPaintFps)), // framerate of OnPaint calls
|
|
SummaryFont, DbgTextColor
|
|
);
|
|
DbgY += DbgDY;
|
|
|
|
//////////////////////////////////////////////////
|
|
// Display the "update" performance info.
|
|
|
|
const uint64 AvgPreUpdateTracksDurationMs = FStopwatch::Cycles64ToMilliseconds(PreUpdateTracksDurationHistory.ComputeAverage());
|
|
const uint64 AvgUpdateTracksDurationMs = FStopwatch::Cycles64ToMilliseconds(UpdateTracksDurationHistory.ComputeAverage());
|
|
const uint64 AvgPostUpdateTracksDurationMs = FStopwatch::Cycles64ToMilliseconds(PostUpdateTracksDurationHistory.ComputeAverage());
|
|
const uint64 AvgTickDurationMs = FStopwatch::Cycles64ToMilliseconds(TickDurationHistory.ComputeAverage());
|
|
|
|
DrawContext.DrawText
|
|
(
|
|
DbgX, DbgY,
|
|
FString::Printf(TEXT("U avg: %llu ms + %llu ms + %llu ms + %llu ms = %llu ms"),
|
|
AvgPreUpdateTracksDurationMs,
|
|
AvgUpdateTracksDurationMs,
|
|
AvgPostUpdateTracksDurationMs,
|
|
AvgTickDurationMs - AvgPreUpdateTracksDurationMs - AvgUpdateTracksDurationMs - AvgPostUpdateTracksDurationMs,
|
|
AvgTickDurationMs),
|
|
SummaryFont, DbgTextColor
|
|
);
|
|
DbgY += DbgDY;
|
|
|
|
//////////////////////////////////////////////////
|
|
// Display timing events stats.
|
|
|
|
DrawContext.DrawText
|
|
(
|
|
DbgX, DbgY,
|
|
FString::Format(TEXT("{0} events : {1} ({2}) boxes, {3} borders, {4} texts"),
|
|
{
|
|
FText::AsNumber(Helper.GetNumEvents()).ToString(),
|
|
FText::AsNumber(Helper.GetNumDrawBoxes()).ToString(),
|
|
FText::AsPercent((double)Helper.GetNumDrawBoxes() / (Helper.GetNumDrawBoxes() + Helper.GetNumMergedBoxes())).ToString(),
|
|
FText::AsNumber(Helper.GetNumDrawBorders()).ToString(),
|
|
FText::AsNumber(Helper.GetNumDrawTexts()).ToString(),
|
|
//OutDrawElements.GetRootDrawLayer().GetElementCount(),
|
|
}),
|
|
SummaryFont, DbgTextColor
|
|
);
|
|
DbgY += DbgDY;
|
|
|
|
//////////////////////////////////////////////////
|
|
// Display time markers stats.
|
|
|
|
if (MarkersTrack->IsVisible())
|
|
{
|
|
DrawContext.DrawText
|
|
(
|
|
DbgX, DbgY,
|
|
FString::Format(TEXT("{0} logs : {1} boxes, {2} texts"),
|
|
{
|
|
FText::AsNumber(MarkersTrack->GetNumLogMessages()).ToString(),
|
|
FText::AsNumber(MarkersTrack->GetNumBoxes()).ToString(),
|
|
FText::AsNumber(MarkersTrack->GetNumTexts()).ToString(),
|
|
}),
|
|
SummaryFont, DbgTextColor
|
|
);
|
|
DbgY += DbgDY;
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
// Display Graph track stats.
|
|
|
|
if (GraphTrack)
|
|
{
|
|
DrawContext.DrawText
|
|
(
|
|
DbgX, DbgY,
|
|
FString::Format(TEXT("{0} events : {1} points, {2} lines, {3} boxes"),
|
|
{
|
|
FText::AsNumber(GraphTrack->GetNumAddedEvents()).ToString(),
|
|
FText::AsNumber(GraphTrack->GetNumDrawPoints()).ToString(),
|
|
FText::AsNumber(GraphTrack->GetNumDrawLines()).ToString(),
|
|
FText::AsNumber(GraphTrack->GetNumDrawBoxes()).ToString(),
|
|
}),
|
|
SummaryFont, DbgTextColor
|
|
);
|
|
DbgY += DbgDY;
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
// Display viewport's horizontal info.
|
|
|
|
DrawContext.DrawText
|
|
(
|
|
DbgX, DbgY,
|
|
FString::Printf(TEXT("SX: %g, ST: %g, ET: %s"),
|
|
Viewport.GetScaleX(),
|
|
Viewport.GetStartTime(),
|
|
*FormatTimeAuto(Viewport.GetMaxValidTime())),
|
|
SummaryFont, DbgTextColor
|
|
);
|
|
DbgY += DbgDY;
|
|
|
|
//////////////////////////////////////////////////
|
|
// Display viewport's vertical info.
|
|
|
|
DrawContext.DrawText
|
|
(
|
|
DbgX, DbgY,
|
|
FString::Printf(TEXT("Y: %.2f, H: %g, VH: %g"),
|
|
Viewport.GetScrollPosY(),
|
|
Viewport.GetScrollHeight(),
|
|
Viewport.GetHeight()),
|
|
SummaryFont, DbgTextColor
|
|
);
|
|
DbgY += DbgDY;
|
|
|
|
//////////////////////////////////////////////////
|
|
// Display input related debug info.
|
|
|
|
FString InputStr = FString::Printf(TEXT("(%.0f, %.0f)"), MousePosition.X, MousePosition.Y);
|
|
if (bIsSpaceBarKeyPressed) InputStr += " Space";
|
|
if (bIsLMB_Pressed) InputStr += " LMB";
|
|
if (bIsRMB_Pressed) InputStr += " RMB";
|
|
if (bIsPanning) InputStr += " Panning";
|
|
if (bIsSelecting) InputStr += " Selecting";
|
|
if (bIsDragging) InputStr += " Dragging";
|
|
if (TimeRulerTrack->IsScrubbing()) InputStr += " Scrubbing";
|
|
DrawContext.DrawText(DbgX, DbgY, InputStr, SummaryFont, DbgTextColor);
|
|
DbgY += DbgDY;
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
|
|
Stopwatch.Stop();
|
|
TotalDrawDurationHistory.AddValue(Stopwatch.AccumulatedTime);
|
|
|
|
return SCompoundWidget::OnPaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled && IsEnabled());
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
const TCHAR* STimingView::GetLocationName(ETimingTrackLocation Location)
|
|
{
|
|
switch (Location)
|
|
{
|
|
case ETimingTrackLocation::TopDocked: return TEXT("Top Docked");
|
|
case ETimingTrackLocation::BottomDocked: return TEXT("Bottom Docked");
|
|
case ETimingTrackLocation::Scrollable: return TEXT("Scrollable");
|
|
case ETimingTrackLocation::Foreground: return TEXT("Foreground");
|
|
default: return nullptr;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::AddTrack(TSharedPtr<FBaseTimingTrack> Track, ETimingTrackLocation Location)
|
|
{
|
|
check(Track.IsValid());
|
|
|
|
check(Location == ETimingTrackLocation::Scrollable ||
|
|
Location == ETimingTrackLocation::TopDocked ||
|
|
Location == ETimingTrackLocation::BottomDocked ||
|
|
Location == ETimingTrackLocation::Foreground);
|
|
|
|
const TCHAR* LocationName = GetLocationName(Location);
|
|
TArray<TSharedPtr<FBaseTimingTrack>>& TrackList = const_cast<TArray<TSharedPtr<FBaseTimingTrack>>&>(GetTrackList(Location));
|
|
|
|
const int32 MaxNumTracks = 1000;
|
|
if (TrackList.Num() >= MaxNumTracks)
|
|
{
|
|
using namespace UE::Insights::TimingProfiler;
|
|
UE_LOG(LogTimingProfiler, Warning, TEXT("Too many tracks already created (%d tracks)! Ignoring %s track : %s (\"%s\")"),
|
|
TrackList.Num(),
|
|
LocationName,
|
|
*Track->GetTypeName().ToString(),
|
|
*Track->GetName());
|
|
return;
|
|
}
|
|
|
|
#if 0
|
|
UE_LOG(LogTimingProfiler, Log, TEXT("New %s Track (%d) : %s (\"%s\")"),
|
|
LocationName,
|
|
TrackList.Num() + 1,
|
|
*Track->GetTypeName().ToString(),
|
|
*Track->GetName());
|
|
#endif
|
|
|
|
ensure(Track->GetLocation() == ETimingTrackLocation::None);
|
|
Track->SetLocation(Location);
|
|
|
|
check(!AllTracks.Contains(Track->GetId()));
|
|
AllTracks.Add(Track->GetId(), Track);
|
|
|
|
TrackList.Add(Track);
|
|
Algo::SortBy(TrackList, &FBaseTimingTrack::GetOrder);
|
|
|
|
if (Location == ETimingTrackLocation::Scrollable)
|
|
{
|
|
InvalidateScrollableTracksOrder();
|
|
}
|
|
|
|
OnTrackAddedDelegate.Broadcast(Track);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool STimingView::RemoveTrack(TSharedPtr<FBaseTimingTrack> Track)
|
|
{
|
|
check(Track.IsValid());
|
|
|
|
if (AllTracks.Remove(Track->GetId()) > 0)
|
|
{
|
|
const ETimingTrackLocation Location = Track->GetLocation();
|
|
check(Location == ETimingTrackLocation::Scrollable ||
|
|
Location == ETimingTrackLocation::TopDocked ||
|
|
Location == ETimingTrackLocation::BottomDocked ||
|
|
Location == ETimingTrackLocation::Foreground);
|
|
|
|
Track->SetLocation(ETimingTrackLocation::None);
|
|
|
|
const TCHAR* LocationName = GetLocationName(Location);
|
|
TArray<TSharedPtr<FBaseTimingTrack>>& TrackList = const_cast<TArray<TSharedPtr<FBaseTimingTrack>>&>(GetTrackList(Location));
|
|
|
|
TrackList.Remove(Track);
|
|
|
|
if (Location == ETimingTrackLocation::Scrollable)
|
|
{
|
|
InvalidateScrollableTracksOrder();
|
|
}
|
|
|
|
OnTrackRemovedDelegate.Broadcast(Track);
|
|
|
|
#if 0
|
|
UE_LOG(LogTimingProfiler, Log, TEXT("Removed %s Track (%d) : %s (\"%s\")"),
|
|
LocationName,
|
|
TrackList.Num(),
|
|
*Track->GetTypeName().ToString(),
|
|
*Track->GetName());
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::HideAllScrollableTracks()
|
|
{
|
|
for (TSharedPtr<FBaseTimingTrack>& Track : ScrollableTracks)
|
|
{
|
|
Track->Hide();
|
|
}
|
|
HandleTrackVisibilityChanged();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::InvalidateScrollableTracksOrder()
|
|
{
|
|
bScrollableTracksOrderIsDirty = true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::UpdateScrollableTracksOrder()
|
|
{
|
|
if (bScrollableTracksOrderIsDirty)
|
|
{
|
|
Algo::SortBy(ScrollableTracks, &FBaseTimingTrack::GetOrder);
|
|
bScrollableTracksOrderIsDirty = false;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int32 STimingView::GetFirstScrollableTrackOrder() const
|
|
{
|
|
return (ScrollableTracks.Num() > 0) ? ScrollableTracks[0]->GetOrder() : 1;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int32 STimingView::GetLastScrollableTrackOrder() const
|
|
{
|
|
return (ScrollableTracks.Num() > 0) ? ScrollableTracks.Last()->GetOrder() : -1;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
FReply STimingView::AllowTracksToProcessOnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
|
{
|
|
for (TSharedPtr<FBaseTimingTrack>& TrackPtr : TopDockedTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
FReply Reply = TrackPtr->OnMouseButtonDown(MyGeometry, MouseEvent);
|
|
if (Reply.IsEventHandled())
|
|
{
|
|
return Reply;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (TSharedPtr<FBaseTimingTrack>& TrackPtr : BottomDockedTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
FReply Reply = TrackPtr->OnMouseButtonDown(MyGeometry, MouseEvent);
|
|
if (Reply.IsEventHandled())
|
|
{
|
|
return Reply;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (TSharedPtr<FBaseTimingTrack>& TrackPtr : ScrollableTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
FReply Reply = TrackPtr->OnMouseButtonDown(MyGeometry, MouseEvent);
|
|
if (Reply.IsEventHandled())
|
|
{
|
|
return Reply;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (TSharedPtr<FBaseTimingTrack>& TrackPtr : ForegroundTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
FReply Reply = TrackPtr->OnMouseButtonDown(MyGeometry, MouseEvent);
|
|
if (Reply.IsEventHandled())
|
|
{
|
|
return Reply;
|
|
}
|
|
}
|
|
}
|
|
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
FReply STimingView::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
|
{
|
|
FReply Reply = AllowTracksToProcessOnMouseButtonDown(MyGeometry, MouseEvent);
|
|
if (Reply.IsEventHandled())
|
|
{
|
|
return Reply;
|
|
}
|
|
|
|
MousePositionOnButtonDown = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
|
|
MousePosition = MousePositionOnButtonDown;
|
|
|
|
if (bAllowPanningOnScreenEdges)
|
|
{
|
|
const FVector2f ScreenSpacePosition = FVector2f(MouseEvent.GetScreenSpacePosition());
|
|
DPIScaleFactor = FPlatformApplicationMisc::GetDPIScaleFactorAtPoint(ScreenSpacePosition.X, ScreenSpacePosition.Y);
|
|
|
|
EdgeFrameCountX = 0;
|
|
EdgeFrameCountY = 0;
|
|
}
|
|
|
|
bool bStartPanningSelectingOrScrubbing = false;
|
|
bool bStartPanning = false;
|
|
bool bStartSelecting = false;
|
|
bool bStartScrubbing = false;
|
|
|
|
if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton)
|
|
{
|
|
if (!bIsRMB_Pressed)
|
|
{
|
|
bIsLMB_Pressed = true;
|
|
bStartPanningSelectingOrScrubbing = true;
|
|
SelectHoveredTimingTrack();
|
|
}
|
|
}
|
|
else if (MouseEvent.GetEffectingButton() == EKeys::RightMouseButton)
|
|
{
|
|
if (!bIsLMB_Pressed)
|
|
{
|
|
bIsRMB_Pressed = true;
|
|
bStartPanningSelectingOrScrubbing = true;
|
|
SelectHoveredTimingTrack();
|
|
}
|
|
}
|
|
|
|
TSharedPtr<FTimeMarker> ScrubbingTimeMarker = nullptr;
|
|
|
|
if (bStartPanningSelectingOrScrubbing)
|
|
{
|
|
bool bIsHoveringTimeRulerTrack = false;
|
|
if (TimeRulerTrack->IsVisible())
|
|
{
|
|
bIsHoveringTimeRulerTrack = MousePositionOnButtonDown.Y >= TimeRulerTrack->GetPosY() &&
|
|
MousePositionOnButtonDown.Y < TimeRulerTrack->GetPosY() + TimeRulerTrack->GetHeight();
|
|
if (bIsHoveringTimeRulerTrack)
|
|
{
|
|
if (MouseEvent.GetModifierKeys().IsControlDown())
|
|
{
|
|
ScrubbingTimeMarker = DefaultTimeMarker;
|
|
}
|
|
else
|
|
{
|
|
ScrubbingTimeMarker = TimeRulerTrack->GetTimeMarkerAtPos(MousePositionOnButtonDown, Viewport);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bIsSpaceBarKeyPressed)
|
|
{
|
|
bStartPanning = true;
|
|
}
|
|
else if (ScrubbingTimeMarker)
|
|
{
|
|
bStartScrubbing = true;
|
|
}
|
|
else if (bIsHoveringTimeRulerTrack || (MouseEvent.GetModifierKeys().IsControlDown() && MouseEvent.GetModifierKeys().IsShiftDown()))
|
|
{
|
|
bStartSelecting = true;
|
|
}
|
|
else
|
|
{
|
|
bStartPanning = true;
|
|
}
|
|
|
|
// Capture mouse, so we can drag outside this widget.
|
|
if (bAllowPanningOnScreenEdges)
|
|
{
|
|
Reply = FReply::Handled().CaptureMouse(SharedThis(this)).UseHighPrecisionMouseMovement(SharedThis(this)).SetUserFocus(SharedThis(this), EFocusCause::Mouse);
|
|
}
|
|
else
|
|
{
|
|
Reply = FReply::Handled().CaptureMouse(SharedThis(this));
|
|
}
|
|
}
|
|
|
|
if (bPreventThrottling)
|
|
{
|
|
Reply.PreventThrottling();
|
|
}
|
|
|
|
if (bStartScrubbing)
|
|
{
|
|
bIsPanning = false;
|
|
bIsDragging = false;
|
|
TimeRulerTrack->StartScrubbing(ScrubbingTimeMarker.ToSharedRef());
|
|
}
|
|
else if (bStartPanning)
|
|
{
|
|
bIsPanning = true;
|
|
bIsDragging = false;
|
|
TimeRulerTrack->StopScrubbing();
|
|
|
|
ViewportStartTimeOnButtonDown = Viewport.GetStartTime();
|
|
ViewportScrollPosYOnButtonDown = Viewport.GetScrollPosY();
|
|
|
|
if (MouseEvent.GetModifierKeys().IsControlDown())
|
|
{
|
|
// Allow panning only horizontally.
|
|
PanningMode = EPanningMode::Horizontal;
|
|
}
|
|
else if (MouseEvent.GetModifierKeys().IsShiftDown())
|
|
{
|
|
// Allow panning only vertically.
|
|
PanningMode = EPanningMode::Vertical;
|
|
}
|
|
else
|
|
{
|
|
// Allow panning both horizontally and vertically.
|
|
PanningMode = EPanningMode::HorizontalAndVertical;
|
|
}
|
|
}
|
|
else if (bStartSelecting)
|
|
{
|
|
bIsSelecting = true;
|
|
bIsDragging = false;
|
|
TimeRulerTrack->StopScrubbing();
|
|
|
|
SelectionStartTime = Viewport.SlateUnitsToTime(static_cast<float>(MousePositionOnButtonDown.X));
|
|
SelectionEndTime = SelectionStartTime;
|
|
LastSelectionType = ESelectionType::None;
|
|
RaiseSelectionChanging();
|
|
}
|
|
|
|
return Reply;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
FReply STimingView::AllowTracksToProcessOnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
|
{
|
|
for (TSharedPtr<FBaseTimingTrack>& TrackPtr : TopDockedTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
FReply Reply = TrackPtr->OnMouseButtonUp(MyGeometry, MouseEvent);
|
|
if (Reply.IsEventHandled())
|
|
{
|
|
return Reply;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (TSharedPtr<FBaseTimingTrack>& TrackPtr : BottomDockedTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
FReply Reply = TrackPtr->OnMouseButtonUp(MyGeometry, MouseEvent);
|
|
if (Reply.IsEventHandled())
|
|
{
|
|
return Reply;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (TSharedPtr<FBaseTimingTrack>& TrackPtr : ScrollableTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
FReply Reply = TrackPtr->OnMouseButtonUp(MyGeometry, MouseEvent);
|
|
if (Reply.IsEventHandled())
|
|
{
|
|
return Reply;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (TSharedPtr<FBaseTimingTrack>& TrackPtr : ForegroundTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
FReply Reply = TrackPtr->OnMouseButtonUp(MyGeometry, MouseEvent);
|
|
if (Reply.IsEventHandled())
|
|
{
|
|
return Reply;
|
|
}
|
|
}
|
|
}
|
|
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
FReply STimingView::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
|
{
|
|
FReply Reply = AllowTracksToProcessOnMouseButtonUp(MyGeometry, MouseEvent);
|
|
if (Reply.IsEventHandled())
|
|
{
|
|
return Reply;
|
|
}
|
|
|
|
MousePositionOnButtonUp = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
|
|
MousePosition = MousePositionOnButtonUp;
|
|
|
|
bool bIsMouseClick = MousePositionOnButtonUp.Equals(MousePositionOnButtonDown, 2.0f);
|
|
|
|
if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton)
|
|
{
|
|
if (bIsLMB_Pressed)
|
|
{
|
|
if (bIsPanning)
|
|
{
|
|
PanningMode = EPanningMode::None;
|
|
bIsPanning = false;
|
|
}
|
|
else if (bIsSelecting)
|
|
{
|
|
SelectTimeInterval(SelectionStartTime, SelectionEndTime - SelectionStartTime);
|
|
bIsSelecting = false;
|
|
bIsMouseClick = false;
|
|
}
|
|
else if (TimeRulerTrack->IsScrubbing())
|
|
{
|
|
RaiseTimeMarkerChanged(TimeRulerTrack->GetScrubbingTimeMarker());
|
|
TimeRulerTrack->StopScrubbing();
|
|
bIsMouseClick = false;
|
|
}
|
|
|
|
if (bIsMouseClick)
|
|
{
|
|
UpdateHoveredTimingEvent(static_cast<float>(MousePositionOnButtonUp.X), static_cast<float>(MousePositionOnButtonUp.Y));
|
|
|
|
if (MouseEvent.GetModifierKeys().IsControlDown())
|
|
{
|
|
if (SelectedEvent.IsValid() && HoveredEvent.IsValid())
|
|
{
|
|
// Select the time region that includes both the current selected event and the new event to be selected.
|
|
const double RegionStartTime = FMath::Min(SelectedEvent->GetStartTime(), HoveredEvent->GetStartTime());
|
|
const double RegionEndTime = FMath::Max(Viewport.RestrictEndTime(SelectedEvent->GetEndTime()), Viewport.RestrictEndTime(HoveredEvent->GetEndTime()));
|
|
SelectTimeInterval(RegionStartTime, RegionEndTime - RegionStartTime);
|
|
}
|
|
}
|
|
|
|
// Select the hovered timing event (if any).
|
|
SelectHoveredTimingTrack();
|
|
SelectHoveredTimingEvent();
|
|
|
|
if (MouseEvent.GetModifierKeys().IsShiftDown())
|
|
{
|
|
ToggleGraphSeries(HoveredEvent);
|
|
}
|
|
|
|
// When clicking on an empty space...
|
|
if (!SelectedEvent.IsValid())
|
|
{
|
|
// ...reset selection.
|
|
SelectionEndTime = SelectionStartTime = 0.0;
|
|
LastSelectionType = ESelectionType::None;
|
|
RaiseSelectionChanged();
|
|
}
|
|
}
|
|
|
|
bIsDragging = false;
|
|
|
|
// Release mouse as we no longer drag.
|
|
Reply = FReply::Handled().ReleaseMouseCapture();
|
|
|
|
bIsLMB_Pressed = false;
|
|
}
|
|
}
|
|
else if (MouseEvent.GetEffectingButton() == EKeys::RightMouseButton)
|
|
{
|
|
if (bIsRMB_Pressed)
|
|
{
|
|
if (bIsPanning)
|
|
{
|
|
PanningMode = EPanningMode::None;
|
|
bIsPanning = false;
|
|
}
|
|
else if (bIsSelecting)
|
|
{
|
|
RaiseSelectionChanged();
|
|
bIsSelecting = false;
|
|
}
|
|
else if (TimeRulerTrack->IsScrubbing())
|
|
{
|
|
RaiseTimeMarkerChanged(TimeRulerTrack->GetScrubbingTimeMarker());
|
|
TimeRulerTrack->StopScrubbing();
|
|
bIsMouseClick = false;
|
|
}
|
|
|
|
if (bIsMouseClick)
|
|
{
|
|
SelectHoveredTimingTrack();
|
|
ShowContextMenu(MouseEvent);
|
|
}
|
|
|
|
bIsDragging = false;
|
|
|
|
// Release mouse as we no longer drag.
|
|
Reply = FReply::Handled().ReleaseMouseCapture();
|
|
|
|
bIsRMB_Pressed = false;
|
|
}
|
|
}
|
|
|
|
return Reply;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
FReply STimingView::AllowTracksToProcessOnMouseButtonDoubleClick(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
|
{
|
|
for (TSharedPtr<FBaseTimingTrack>& TrackPtr : TopDockedTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
FReply Reply = TrackPtr->OnMouseButtonDoubleClick(MyGeometry, MouseEvent);
|
|
if (Reply.IsEventHandled())
|
|
{
|
|
return Reply;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (TSharedPtr<FBaseTimingTrack>& TrackPtr : BottomDockedTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
FReply Reply = TrackPtr->OnMouseButtonDoubleClick(MyGeometry, MouseEvent);
|
|
if (Reply.IsEventHandled())
|
|
{
|
|
return Reply;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (TSharedPtr<FBaseTimingTrack>& TrackPtr : ScrollableTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
FReply Reply = TrackPtr->OnMouseButtonDoubleClick(MyGeometry, MouseEvent);
|
|
if (Reply.IsEventHandled())
|
|
{
|
|
return Reply;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (TSharedPtr<FBaseTimingTrack>& TrackPtr : ForegroundTracks)
|
|
{
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
FReply Reply = TrackPtr->OnMouseButtonDoubleClick(MyGeometry, MouseEvent);
|
|
if (Reply.IsEventHandled())
|
|
{
|
|
return Reply;
|
|
}
|
|
}
|
|
}
|
|
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
FReply STimingView::OnMouseButtonDoubleClick(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
|
{
|
|
FReply Reply = AllowTracksToProcessOnMouseButtonDoubleClick(MyGeometry, MouseEvent);
|
|
if (Reply.IsEventHandled())
|
|
{
|
|
return Reply;
|
|
}
|
|
|
|
if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton)
|
|
{
|
|
if (HoveredEvent.IsValid())
|
|
{
|
|
if (MouseEvent.GetModifierKeys().IsControlDown())
|
|
{
|
|
const double EndTime = Viewport.RestrictEndTime(HoveredEvent->GetEndTime());
|
|
SelectTimeInterval(HoveredEvent->GetStartTime(), EndTime - HoveredEvent->GetStartTime());
|
|
}
|
|
else
|
|
{
|
|
if (HoveredEvent->Is<FTimingEvent>() &&
|
|
IsFilterByEventType(HoveredEvent->As<FTimingEvent>().GetType()))
|
|
{
|
|
SetEventFilter(nullptr); // reset filter
|
|
}
|
|
else
|
|
{
|
|
SetEventFilter(HoveredEvent->GetTrack()->GetFilterByEvent(HoveredEvent));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (TimingEventFilter.IsValid())
|
|
{
|
|
TimingEventFilter.Reset();
|
|
Viewport.AddDirtyFlags(ETimingTrackViewportDirtyFlags::HInvalidated);
|
|
}
|
|
}
|
|
|
|
Reply = FReply::Handled();
|
|
}
|
|
|
|
return Reply;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
FReply STimingView::OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
|
{
|
|
FReply Reply = FReply::Unhandled();
|
|
|
|
MousePosition = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
|
|
|
|
const FVector2D& CursorDelta = MouseEvent.GetCursorDelta();
|
|
|
|
if (bIsPanning && bAllowPanningOnScreenEdges)
|
|
{
|
|
if (MouseEvent.GetScreenSpacePosition().X == MouseEvent.GetLastScreenSpacePosition().X)
|
|
{
|
|
++EdgeFrameCountX;
|
|
}
|
|
else
|
|
{
|
|
EdgeFrameCountX = 0;
|
|
}
|
|
|
|
if (EdgeFrameCountX > 10) // handle delay between high precision mouse movement and update of the actual cursor position
|
|
{
|
|
MousePositionOnButtonDown.X -= CursorDelta.X / DPIScaleFactor;
|
|
}
|
|
|
|
if (MouseEvent.GetScreenSpacePosition().Y == MouseEvent.GetLastScreenSpacePosition().Y)
|
|
{
|
|
++EdgeFrameCountY;
|
|
}
|
|
else
|
|
{
|
|
EdgeFrameCountY = 0;
|
|
}
|
|
|
|
if (EdgeFrameCountY > 10) // handle delay between high precision mouse movement and update of the actual cursor position
|
|
{
|
|
MousePositionOnButtonDown.Y -= CursorDelta.Y / DPIScaleFactor;
|
|
}
|
|
}
|
|
|
|
if (!CursorDelta.IsZero())
|
|
{
|
|
if (bIsPanning)
|
|
{
|
|
if (HasMouseCapture())
|
|
{
|
|
bIsDragging = true;
|
|
|
|
if ((int32)PanningMode & (int32)EPanningMode::Horizontal)
|
|
{
|
|
const double StartTime = ViewportStartTimeOnButtonDown + static_cast<double>(MousePositionOnButtonDown.X - MousePosition.X) / Viewport.GetScaleX();
|
|
ScrollAtTime(StartTime);
|
|
}
|
|
|
|
if ((int32)PanningMode & (int32)EPanningMode::Vertical)
|
|
{
|
|
const float ScrollPosY = ViewportScrollPosYOnButtonDown + static_cast<float>(MousePositionOnButtonDown.Y - MousePosition.Y);
|
|
ScrollAtPosY(ScrollPosY);
|
|
}
|
|
}
|
|
}
|
|
else if (bIsSelecting)
|
|
{
|
|
if (HasMouseCapture())
|
|
{
|
|
bIsDragging = true;
|
|
|
|
SelectionStartTime = Viewport.SlateUnitsToTime(static_cast<float>(MousePositionOnButtonDown.X));
|
|
SelectionEndTime = Viewport.SlateUnitsToTime(static_cast<float>(MousePosition.X));
|
|
if (SelectionStartTime > SelectionEndTime)
|
|
{
|
|
double Temp = SelectionStartTime;
|
|
SelectionStartTime = SelectionEndTime;
|
|
SelectionEndTime = Temp;
|
|
}
|
|
LastSelectionType = ESelectionType::TimeRange;
|
|
RaiseSelectionChanging();
|
|
}
|
|
}
|
|
else if (TimeRulerTrack->IsScrubbing())
|
|
{
|
|
if (HasMouseCapture())
|
|
{
|
|
bIsDragging = true;
|
|
double Time = Viewport.SlateUnitsToTime(static_cast<float>(MousePosition.X));
|
|
|
|
// Snap to markers.
|
|
const bool bSnapTimeMarkers = MarkersTrack->IsVisible();
|
|
if (bSnapTimeMarkers)
|
|
{
|
|
const double SnapTolerance = 5.0 / Viewport.GetScaleX(); // +/- 5 pixels
|
|
Time = MarkersTrack->Snap(Time, SnapTolerance);
|
|
}
|
|
|
|
TSharedRef<FTimeMarker> ScrubbingTimeMarker = TimeRulerTrack->GetScrubbingTimeMarker();
|
|
ScrubbingTimeMarker->SetTime(Time);
|
|
RaiseTimeMarkerChanging(ScrubbingTimeMarker);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UpdateHoveredTimingEvent(static_cast<float>(MousePosition.X), static_cast<float>(MousePosition.Y));
|
|
}
|
|
|
|
Reply = FReply::Handled();
|
|
}
|
|
|
|
return Reply;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
|
{
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::OnMouseLeave(const FPointerEvent& MouseEvent)
|
|
{
|
|
if (!HasMouseCapture())
|
|
{
|
|
// No longer dragging (unless we have mouse capture).
|
|
bIsDragging = false;
|
|
bIsPanning = false;
|
|
bIsSelecting = false;
|
|
|
|
bIsLMB_Pressed = false;
|
|
bIsRMB_Pressed = false;
|
|
|
|
MousePosition = FVector2D::ZeroVector;
|
|
|
|
if (HoveredTrack.IsValid())
|
|
{
|
|
HoveredTrack.Reset();
|
|
OnHoveredTrackChangedDelegate.Broadcast(HoveredTrack);
|
|
}
|
|
if (HoveredEvent.IsValid())
|
|
{
|
|
HoveredEvent.Reset();
|
|
OnHoveredEventChangedDelegate.Broadcast(HoveredEvent);
|
|
}
|
|
Tooltip.SetDesiredOpacity(0.0f);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
FReply STimingView::OnMouseWheel(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
|
{
|
|
if (MouseEvent.GetModifierKeys().IsShiftDown())
|
|
{
|
|
if (GraphTrack->IsVisible() &&
|
|
MousePosition.Y >= GraphTrack->GetPosY() &&
|
|
MousePosition.Y < GraphTrack->GetPosY() + GraphTrack->GetHeight())
|
|
{
|
|
// Zoom in/out vertically.
|
|
const double Delta = MouseEvent.GetWheelDelta();
|
|
constexpr double ZoomStep = 0.25; // as percent
|
|
constexpr double MinScaleY = 0.0001;
|
|
constexpr double MaxScaleY = 1.0e10;
|
|
double ScaleY = GraphTrack->GetSharedValueViewport().GetScaleY();
|
|
if (Delta > 0)
|
|
{
|
|
ScaleY *= FMath::Pow(1.0 + ZoomStep, Delta);
|
|
if (ScaleY > MaxScaleY)
|
|
{
|
|
ScaleY = MaxScaleY;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ScaleY *= FMath::Pow(1.0 / (1.0 + ZoomStep), -Delta);
|
|
if (ScaleY < MinScaleY)
|
|
{
|
|
ScaleY = MinScaleY;
|
|
}
|
|
}
|
|
GraphTrack->GetSharedValueViewport().SetScaleY(ScaleY);
|
|
|
|
for (const TSharedPtr<FGraphSeries>& Series : GraphTrack->GetSeries())
|
|
{
|
|
if (Series->IsUsingSharedViewport())
|
|
{
|
|
Series->SetScaleY(ScaleY);
|
|
Series->SetDirtyFlag();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Scroll vertically.
|
|
constexpr float ScrollSpeedY = 16.0f * 3;
|
|
const float NewScrollPosY = Viewport.GetScrollPosY() - ScrollSpeedY * MouseEvent.GetWheelDelta();
|
|
ScrollAtPosY(EnforceVerticalScrollLimits(NewScrollPosY));
|
|
}
|
|
}
|
|
else if (MouseEvent.GetModifierKeys().IsControlDown())
|
|
{
|
|
if (HoveredTrack.IsValid())
|
|
{
|
|
SelectHoveredTimingTrack();
|
|
}
|
|
|
|
// Scroll horizontally.
|
|
const double ScrollSpeedX = Viewport.GetDurationForViewportDX(16.0 * 3);
|
|
const double NewStartTime = Viewport.GetStartTime() - ScrollSpeedX * MouseEvent.GetWheelDelta();
|
|
ScrollAtTime(EnforceHorizontalScrollLimits(NewStartTime));
|
|
}
|
|
else
|
|
{
|
|
if (HoveredTrack.IsValid())
|
|
{
|
|
SelectHoveredTimingTrack();
|
|
}
|
|
|
|
// Zoom in/out horizontally.
|
|
const float Delta = MouseEvent.GetWheelDelta();
|
|
if (Viewport.RelativeZoomWithFixedX(Delta, static_cast<float>(MousePosition.X)))
|
|
{
|
|
UpdateHorizontalScrollBar();
|
|
}
|
|
}
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::OnDragEnter(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent)
|
|
{
|
|
SCompoundWidget::OnDragEnter(MyGeometry, DragDropEvent);
|
|
|
|
//TSharedPtr<FStatIDDragDropOp> Operation = DragDropEvent.GetOperationAs<FStatIDDragDropOp>();
|
|
//if (Operation.IsValid())
|
|
//{
|
|
// Operation->ShowOK();
|
|
//}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::OnDragLeave(const FDragDropEvent& DragDropEvent)
|
|
{
|
|
SCompoundWidget::OnDragLeave(DragDropEvent);
|
|
|
|
//TSharedPtr<FStatIDDragDropOp> Operation = DragDropEvent.GetOperationAs<FStatIDDragDropOp>();
|
|
//if (Operation.IsValid())
|
|
//{
|
|
// Operation->ShowError();
|
|
//}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
FReply STimingView::OnDragOver(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent)
|
|
{
|
|
return SCompoundWidget::OnDragOver(MyGeometry,DragDropEvent);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
FReply STimingView::OnDrop(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent)
|
|
{
|
|
//TSharedPtr<FStatIDDragDropOp> Operation = DragDropEvent.GetOperationAs<FStatIDDragDropOp>();
|
|
//if (Operation.IsValid())
|
|
//{
|
|
// return FReply::Handled();
|
|
//}
|
|
|
|
return SCompoundWidget::OnDrop(MyGeometry,DragDropEvent);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
FCursorReply STimingView::OnCursorQuery(const FGeometry& MyGeometry, const FPointerEvent& CursorEvent) const
|
|
{
|
|
if (bIsPanning)
|
|
{
|
|
if (bIsDragging)
|
|
{
|
|
//return FCursorReply::Cursor(EMouseCursor::GrabHandClosed);
|
|
return FCursorReply::Cursor(EMouseCursor::GrabHand);
|
|
}
|
|
}
|
|
else if (bIsSelecting)
|
|
{
|
|
if (bIsDragging)
|
|
{
|
|
return FCursorReply::Cursor(EMouseCursor::ResizeLeftRight);
|
|
}
|
|
}
|
|
else if (bIsSpaceBarKeyPressed)
|
|
{
|
|
return FCursorReply::Cursor(EMouseCursor::GrabHand);
|
|
}
|
|
|
|
return FCursorReply::Unhandled();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
FReply STimingView::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent)
|
|
{
|
|
if (InKeyEvent.GetKey() == EKeys::B)
|
|
{
|
|
if (!InKeyEvent.GetModifierKeys().IsControlDown() && !InKeyEvent.GetModifierKeys().IsShiftDown())
|
|
{
|
|
// Toggle Bookmarks.
|
|
if (MarkersTrack->IsVisible())
|
|
{
|
|
if (!MarkersTrack->IsBookmarksTrack())
|
|
{
|
|
SetDrawOnlyBookmarks(true);
|
|
}
|
|
else
|
|
{
|
|
SetTimeMarkersVisible(false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetTimeMarkersVisible(true);
|
|
SetDrawOnlyBookmarks(true);
|
|
}
|
|
return FReply::Handled();
|
|
}
|
|
}
|
|
else if (InKeyEvent.GetKey() == EKeys::M)
|
|
{
|
|
if (!InKeyEvent.GetModifierKeys().IsControlDown() && !InKeyEvent.GetModifierKeys().IsShiftDown())
|
|
{
|
|
// Toggle Time Markers.
|
|
if (MarkersTrack->IsVisible())
|
|
{
|
|
if (MarkersTrack->IsBookmarksTrack())
|
|
{
|
|
SetDrawOnlyBookmarks(false);
|
|
}
|
|
else
|
|
{
|
|
SetTimeMarkersVisible(false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetTimeMarkersVisible(true);
|
|
SetDrawOnlyBookmarks(false);
|
|
}
|
|
|
|
return FReply::Handled();
|
|
}
|
|
}
|
|
else if (InKeyEvent.GetKey() == EKeys::F)
|
|
{
|
|
if (!InKeyEvent.GetModifierKeys().IsControlDown() && !InKeyEvent.GetModifierKeys().IsShiftDown())
|
|
{
|
|
FrameSelection();
|
|
return FReply::Handled();
|
|
}
|
|
}
|
|
else if (InKeyEvent.GetKey() == EKeys::C)
|
|
{
|
|
if (InKeyEvent.GetModifierKeys().IsControlDown())
|
|
{
|
|
if (SelectedEvent.IsValid())
|
|
{
|
|
SelectedEvent->GetTrack()->OnClipboardCopyEvent(*SelectedEvent);
|
|
}
|
|
return FReply::Handled();
|
|
}
|
|
}
|
|
else if (InKeyEvent.GetKey() == EKeys::Equals ||
|
|
InKeyEvent.GetKey() == EKeys::Add)
|
|
{
|
|
// Zoom In
|
|
const double ScaleX = Viewport.GetScaleX() * 1.25;
|
|
if (Viewport.ZoomWithFixedX(ScaleX, Viewport.GetWidth() / 2))
|
|
{
|
|
UpdateHorizontalScrollBar();
|
|
}
|
|
return FReply::Handled();
|
|
}
|
|
else if (InKeyEvent.GetKey() == EKeys::Hyphen ||
|
|
InKeyEvent.GetKey() == EKeys::Subtract)
|
|
{
|
|
// Zoom Out
|
|
const double ScaleX = Viewport.GetScaleX() / 1.25;
|
|
if (Viewport.ZoomWithFixedX(ScaleX, Viewport.GetWidth() / 2))
|
|
{
|
|
UpdateHorizontalScrollBar();
|
|
}
|
|
return FReply::Handled();
|
|
}
|
|
else if (InKeyEvent.GetKey() == EKeys::Left)
|
|
{
|
|
if (InKeyEvent.GetModifierKeys().IsControlDown())
|
|
{
|
|
// Scroll Left
|
|
const double NewStartTime = Viewport.GetStartTime() - Viewport.GetDuration() * 0.05;
|
|
ScrollAtTime(EnforceHorizontalScrollLimits(NewStartTime));
|
|
}
|
|
else
|
|
{
|
|
SelectLeftTimingEvent();
|
|
}
|
|
return FReply::Handled();
|
|
}
|
|
else if (InKeyEvent.GetKey() == EKeys::Right)
|
|
{
|
|
if (InKeyEvent.GetModifierKeys().IsControlDown())
|
|
{
|
|
// Scroll Right
|
|
const double NewStartTime = Viewport.GetStartTime() + Viewport.GetDuration() * 0.05;
|
|
ScrollAtTime(EnforceHorizontalScrollLimits(NewStartTime));
|
|
}
|
|
else
|
|
{
|
|
SelectRightTimingEvent();
|
|
}
|
|
return FReply::Handled();
|
|
}
|
|
else if (InKeyEvent.GetKey() == EKeys::Up)
|
|
{
|
|
if (InKeyEvent.GetModifierKeys().IsControlDown())
|
|
{
|
|
// Scroll Up
|
|
const float NewScrollPosY = Viewport.GetScrollPosY() - 16.0f * 3;
|
|
ScrollAtPosY(EnforceVerticalScrollLimits(NewScrollPosY));
|
|
}
|
|
else
|
|
{
|
|
SelectUpTimingEvent();
|
|
}
|
|
return FReply::Handled();
|
|
}
|
|
else if (InKeyEvent.GetKey() == EKeys::Down)
|
|
{
|
|
if (InKeyEvent.GetModifierKeys().IsControlDown())
|
|
{
|
|
// Scroll Down
|
|
const float NewScrollPosY = Viewport.GetScrollPosY() + 16.0f * 3;
|
|
ScrollAtPosY(EnforceVerticalScrollLimits(NewScrollPosY));
|
|
}
|
|
else
|
|
{
|
|
SelectDownTimingEvent();
|
|
}
|
|
return FReply::Handled();
|
|
}
|
|
else if (InKeyEvent.GetKey() == EKeys::Enter)
|
|
{
|
|
// Enter: Selects the time range of the currently selected timing event.
|
|
if (SelectedEvent.IsValid())
|
|
{
|
|
const double Duration = Viewport.RestrictDuration(SelectedEvent->GetStartTime(), SelectedEvent->GetEndTime());
|
|
SelectTimeInterval(SelectedEvent->GetStartTime(), Duration);
|
|
}
|
|
return FReply::Handled();
|
|
}
|
|
else if (InKeyEvent.GetKey() == EKeys::SpaceBar)
|
|
{
|
|
bIsSpaceBarKeyPressed = true;
|
|
FSlateApplication::Get().QueryCursor();
|
|
return FReply::Handled();
|
|
}
|
|
else if (InKeyEvent.GetKey() == EKeys::D)
|
|
{
|
|
if (InKeyEvent.GetModifierKeys().IsControlDown() && InKeyEvent.GetModifierKeys().IsShiftDown())
|
|
{
|
|
// Ctrl+Shift+D: Toggles down-sampling on/off (for debugging purposes only).
|
|
FTimingEventsTrack::bUseDownSampling = !FTimingEventsTrack::bUseDownSampling;
|
|
Viewport.AddDirtyFlags(ETimingTrackViewportDirtyFlags::HInvalidated);
|
|
return FReply::Handled();
|
|
}
|
|
}
|
|
else if (InKeyEvent.GetKey() == EKeys::A)
|
|
{
|
|
if (InKeyEvent.GetModifierKeys().IsControlDown())
|
|
{
|
|
// Ctrl+A: Selects the entire time range of session.
|
|
SelectTimeInterval(0, Viewport.GetMaxValidTime());
|
|
return FReply::Handled();
|
|
}
|
|
}
|
|
else if (InKeyEvent.GetKey() == EKeys::One)
|
|
{
|
|
LoadingSharedState->SetColorSchema(0);
|
|
Viewport.AddDirtyFlags(ETimingTrackViewportDirtyFlags::HInvalidated);
|
|
return FReply::Handled();
|
|
}
|
|
else if (InKeyEvent.GetKey() == EKeys::Two)
|
|
{
|
|
LoadingSharedState->SetColorSchema(1);
|
|
Viewport.AddDirtyFlags(ETimingTrackViewportDirtyFlags::HInvalidated);
|
|
return FReply::Handled();
|
|
}
|
|
else if (InKeyEvent.GetKey() == EKeys::Three)
|
|
{
|
|
LoadingSharedState->SetColorSchema(2);
|
|
Viewport.AddDirtyFlags(ETimingTrackViewportDirtyFlags::HInvalidated);
|
|
return FReply::Handled();
|
|
}
|
|
else if (InKeyEvent.GetKey() == EKeys::Four)
|
|
{
|
|
LoadingSharedState->SetColorSchema(3);
|
|
Viewport.AddDirtyFlags(ETimingTrackViewportDirtyFlags::HInvalidated);
|
|
return FReply::Handled();
|
|
}
|
|
else if (InKeyEvent.GetKey() == EKeys::X)
|
|
{
|
|
ChooseNextEventDepthLimit();
|
|
return FReply::Handled();
|
|
}
|
|
#if INSIGHTS_ACTIVATE_BENCHMARK
|
|
else if (InKeyEvent.GetKey() == EKeys::Z)
|
|
{
|
|
FTimingProfilerTests::FCheckValues CheckValues;
|
|
FTimingProfilerTests::RunEnumerateBenchmark(FTimingProfilerTests::FEnumerateTestParams(), CheckValues);
|
|
return FReply::Handled();
|
|
}
|
|
#endif
|
|
|
|
if (CommandList->ProcessCommandBindings(InKeyEvent))
|
|
{
|
|
return FReply::Handled();
|
|
}
|
|
|
|
return SCompoundWidget::OnKeyDown(MyGeometry, InKeyEvent);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
FReply STimingView::OnKeyUp(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent)
|
|
{
|
|
if (InKeyEvent.GetKey() == EKeys::SpaceBar)
|
|
{
|
|
bIsSpaceBarKeyPressed = false;
|
|
FSlateApplication::Get().QueryCursor();
|
|
return FReply::Handled();
|
|
}
|
|
|
|
return SCompoundWidget::OnKeyUp(MyGeometry, InKeyEvent);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::ShowContextMenu(const FPointerEvent& MouseEvent)
|
|
{
|
|
using namespace UE::Insights::TimingProfiler;
|
|
const FTimingViewCommands& Commands = FTimingViewCommands::Get();
|
|
|
|
const bool bShouldCloseWindowAfterMenuSelection = true;
|
|
FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, CommandList);
|
|
|
|
bool bHasAnyActions = false;
|
|
|
|
if (HoveredTrack.IsValid())
|
|
{
|
|
MenuBuilder.BeginSection(TEXT("Track"), FText::FromString(HoveredTrack->GetName()));
|
|
CreateTrackLocationMenu(MenuBuilder, HoveredTrack.ToSharedRef());
|
|
MenuBuilder.EndSection();
|
|
|
|
HoveredTrack->BuildContextMenu(MenuBuilder);
|
|
MenuBuilder.AddSeparator();
|
|
bHasAnyActions = true;
|
|
}
|
|
|
|
MenuBuilder.BeginSection(TEXT("Misc"));
|
|
{
|
|
MenuBuilder.AddMenuEntry(Commands.QuickFind,
|
|
FName("QuickFind"),
|
|
TAttribute<FText>(),
|
|
TAttribute<FText>(),
|
|
FSlateIcon(FInsightsStyle::GetStyleSetName(), "Icons.Find"));
|
|
|
|
TSharedPtr<SLogView> LogView = GetLogView();
|
|
const double MousePosTime = Viewport.SlateUnitsToTime(static_cast<float>(MousePosition.X));
|
|
const FText MousePosTimeText = FText::FromString(FormatTimeAuto(MousePosTime, 2));
|
|
const FText Label = FText::Format(LOCTEXT("ContextMenu_ScrollLogView_Fmt", "Scroll Log View (\u2192 {0})"), MousePosTimeText);
|
|
MenuBuilder.AddMenuEntry(
|
|
Label,
|
|
FText::Format(LOCTEXT("ContextMenu_ScrollLogView_Desc_Fmt", "Scrolls the Log View at the message with the closest timestamp to the time of the current mouse position ({0})."), MousePosTimeText),
|
|
FSlateIcon(FInsightsStyle::GetStyleSetName(), "Icons.LogView"),
|
|
FUIAction(
|
|
FExecuteAction::CreateLambda([LogView, MousePosTime]()
|
|
{
|
|
if (LogView.IsValid())
|
|
{
|
|
LogView->SelectLogMessageByClosestTime(MousePosTime);
|
|
}
|
|
}),
|
|
FCanExecuteAction::CreateLambda([LogView]() -> bool
|
|
{
|
|
return LogView.IsValid();
|
|
})),
|
|
NAME_None,
|
|
EUserInterfaceActionType::Button
|
|
);
|
|
|
|
if (HoveredEvent)
|
|
{
|
|
double RangeStart = HoveredEvent->GetStartTime();
|
|
double RangeDuration = Viewport.RestrictEndTime(HoveredEvent->GetEndTime()) - RangeStart;
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("ContextMenu_SelectEventTimeRange", "Select Time Range of Event"),
|
|
FText(),
|
|
FSlateIcon(FInsightsStyle::GetStyleSetName(), "Icons.SelectEventRange"),
|
|
FUIAction(FExecuteAction::CreateLambda([this, RangeStart, RangeDuration]()
|
|
{
|
|
SelectTimeInterval(RangeStart, RangeDuration);
|
|
})),
|
|
NAME_None,
|
|
EUserInterfaceActionType::Button
|
|
);
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("ContextMenu_Copy", "Copy"),
|
|
FText(),
|
|
FSlateIcon(FAppStyle::Get().GetStyleSetName(), "GenericCommands.Copy"),
|
|
FUIAction(FExecuteAction::CreateLambda([Event=HoveredEvent]()
|
|
{
|
|
Event->GetTrack()->OnClipboardCopyEvent(*Event);
|
|
})),
|
|
NAME_None,
|
|
EUserInterfaceActionType::Button
|
|
);
|
|
}
|
|
|
|
bHasAnyActions = true;
|
|
}
|
|
MenuBuilder.EndSection();
|
|
|
|
for (Timing::ITimingViewExtender* Extender : GetExtenders())
|
|
{
|
|
bHasAnyActions |= Extender->ExtendGlobalContextMenu(*this, MenuBuilder);
|
|
}
|
|
#if UE_INSIGHTS_BACKWARD_COMPATIBILITY_UE54
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
::Insights::ITimingViewSession* CurrentTimingViewSession = (::Insights::ITimingViewSession*)(Timing::ITimingViewSession*)this;
|
|
for (::Insights::ITimingViewExtender* Extender : GetOldExtenders())
|
|
{
|
|
bHasAnyActions |= Extender->ExtendGlobalContextMenu(*CurrentTimingViewSession, MenuBuilder);
|
|
}
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
#endif // UE_INSIGHTS_BACKWARD_COMPATIBILITY_UE54
|
|
|
|
if (!bHasAnyActions)
|
|
{
|
|
MenuBuilder.BeginSection(TEXT("Empty"));
|
|
{
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("ContextMenu_NA", "N/A"),
|
|
LOCTEXT("ContextMenu_NA_Desc", "No action available."),
|
|
FSlateIcon(),
|
|
FUIAction(FExecuteAction(), FCanExecuteAction::CreateLambda([](){ return false; })),
|
|
NAME_None,
|
|
EUserInterfaceActionType::Button
|
|
);
|
|
}
|
|
MenuBuilder.EndSection();
|
|
}
|
|
|
|
TSharedRef<SWidget> MenuWidget = MenuBuilder.MakeWidget();
|
|
|
|
FWidgetPath EventPath = MouseEvent.GetEventPath() != nullptr ? *MouseEvent.GetEventPath() : FWidgetPath();
|
|
const FVector2D ScreenSpacePosition = MouseEvent.GetScreenSpacePosition();
|
|
FSlateApplication::Get().PushMenu(SharedThis(this), EventPath, MenuWidget, ScreenSpacePosition, FPopupTransitionEffect::ContextMenu);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::CreateTrackLocationMenu(FMenuBuilder& MenuBuilder, TSharedRef<FBaseTimingTrack> Track)
|
|
{
|
|
if (EnumHasAnyFlags(Track->GetValidLocations(), ETimingTrackLocation::TopDocked))
|
|
{
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("TrackLocationMenu_DockToTop", "Top Docked"),
|
|
LOCTEXT("TrackLocationMenu_DockToTop_Desc", "Dock this track to the top."),
|
|
FSlateIcon(),
|
|
FUIAction(
|
|
FExecuteAction::CreateSP(this, &STimingView::ChangeTrackLocation, Track, ETimingTrackLocation::TopDocked),
|
|
FCanExecuteAction::CreateSP(this, &STimingView::CanChangeTrackLocation, Track, ETimingTrackLocation::TopDocked),
|
|
FIsActionChecked::CreateSP(this, &STimingView::CheckTrackLocation, Track, ETimingTrackLocation::TopDocked)),
|
|
NAME_None,
|
|
EUserInterfaceActionType::RadioButton
|
|
);
|
|
}
|
|
|
|
if (EnumHasAnyFlags(Track->GetValidLocations(), ETimingTrackLocation::Scrollable))
|
|
{
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("TrackLocationMenu_MoveToScrollable", "Scrollable"),
|
|
LOCTEXT("TrackLocationMenu_MoveToScrollable_Desc", "Move this track to the list of scrollable tracks."),
|
|
FSlateIcon(),
|
|
FUIAction(
|
|
FExecuteAction::CreateSP(this, &STimingView::ChangeTrackLocation, Track, ETimingTrackLocation::Scrollable),
|
|
FCanExecuteAction::CreateSP(this, &STimingView::CanChangeTrackLocation, Track, ETimingTrackLocation::Scrollable),
|
|
FIsActionChecked::CreateSP(this, &STimingView::CheckTrackLocation, Track, ETimingTrackLocation::Scrollable)),
|
|
NAME_None,
|
|
EUserInterfaceActionType::RadioButton
|
|
);
|
|
}
|
|
|
|
if (EnumHasAnyFlags(Track->GetValidLocations(), ETimingTrackLocation::BottomDocked))
|
|
{
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("TrackLocationMenu_DockToBottom", "Bottom Docked"),
|
|
LOCTEXT("TrackLocationMenu_DockToBottom_Desc", "Dock this track to the bottom."),
|
|
FSlateIcon(),
|
|
FUIAction(
|
|
FExecuteAction::CreateSP(this, &STimingView::ChangeTrackLocation, Track, ETimingTrackLocation::BottomDocked),
|
|
FCanExecuteAction::CreateSP(this, &STimingView::CanChangeTrackLocation, Track, ETimingTrackLocation::BottomDocked),
|
|
FIsActionChecked::CreateSP(this, &STimingView::CheckTrackLocation, Track, ETimingTrackLocation::BottomDocked)),
|
|
NAME_None,
|
|
EUserInterfaceActionType::RadioButton
|
|
);
|
|
}
|
|
|
|
if (EnumHasAnyFlags(Track->GetValidLocations(), ETimingTrackLocation::Foreground))
|
|
{
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("TrackLocationMenu_MoveToForeground", "Foreground"),
|
|
LOCTEXT("TrackLocationMenu_MoveToForeground_Desc", "Move this track to the list of foreground tracks."),
|
|
FSlateIcon(),
|
|
FUIAction(
|
|
FExecuteAction::CreateSP(this, &STimingView::ChangeTrackLocation, Track, ETimingTrackLocation::Foreground),
|
|
FCanExecuteAction::CreateSP(this, &STimingView::CanChangeTrackLocation, Track, ETimingTrackLocation::Foreground),
|
|
FIsActionChecked::CreateSP(this, &STimingView::CheckTrackLocation, Track, ETimingTrackLocation::Foreground)),
|
|
NAME_None,
|
|
EUserInterfaceActionType::Button
|
|
);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::EnumerateAllTracks(TFunctionRef<bool(TSharedPtr<FBaseTimingTrack>&)> Callback)
|
|
{
|
|
for (TSharedPtr<FBaseTimingTrack>& TrackPtr : TopDockedTracks)
|
|
{
|
|
if (!Callback(TrackPtr))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
for (TSharedPtr<FBaseTimingTrack>& TrackPtr : ScrollableTracks)
|
|
{
|
|
if (!Callback(TrackPtr))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
for (TSharedPtr<FBaseTimingTrack>& TrackPtr : BottomDockedTracks)
|
|
{
|
|
if (!Callback(TrackPtr))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
for (TSharedPtr<FBaseTimingTrack>& TrackPtr : ForegroundTracks)
|
|
{
|
|
if (!Callback(TrackPtr))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::ChangeTrackLocation(TSharedRef<FBaseTimingTrack> Track, ETimingTrackLocation NewLocation)
|
|
{
|
|
if (EnumHasAnyFlags(Track->GetValidLocations(), NewLocation) &&
|
|
Track->GetLocation() != NewLocation)
|
|
{
|
|
switch (Track->GetLocation())
|
|
{
|
|
case ETimingTrackLocation::Scrollable:
|
|
ensure(RemoveScrollableTrack(Track));
|
|
break;
|
|
|
|
case ETimingTrackLocation::TopDocked:
|
|
ensure(RemoveTopDockedTrack(Track));
|
|
break;
|
|
|
|
case ETimingTrackLocation::BottomDocked:
|
|
ensure(RemoveBottomDockedTrack(Track));
|
|
break;
|
|
|
|
case ETimingTrackLocation::Foreground:
|
|
ensure(RemoveForegroundTrack(Track));
|
|
break;
|
|
}
|
|
|
|
switch (NewLocation)
|
|
{
|
|
case ETimingTrackLocation::Scrollable:
|
|
AddScrollableTrack(Track);
|
|
break;
|
|
|
|
case ETimingTrackLocation::TopDocked:
|
|
AddTopDockedTrack(Track);
|
|
break;
|
|
|
|
case ETimingTrackLocation::BottomDocked:
|
|
AddBottomDockedTrack(Track);
|
|
break;
|
|
|
|
case ETimingTrackLocation::Foreground:
|
|
AddForegroundTrack(Track);
|
|
break;
|
|
}
|
|
|
|
HandleTrackVisibilityChanged();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool STimingView::CanChangeTrackLocation(TSharedRef<FBaseTimingTrack> Track, ETimingTrackLocation NewLocation) const
|
|
{
|
|
return EnumHasAnyFlags(Track->GetValidLocations(), NewLocation);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool STimingView::CheckTrackLocation(TSharedRef<FBaseTimingTrack> Track, ETimingTrackLocation Location) const
|
|
{
|
|
return Track->GetLocation() == Location;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::BindCommands()
|
|
{
|
|
using namespace UE::Insights::TimingProfiler;
|
|
FTimingViewCommands::Register();
|
|
|
|
const FTimingViewCommands& Commands = FTimingViewCommands::Get();
|
|
|
|
CommandList = MakeShared<FUICommandList>();
|
|
|
|
CommandList->MapAction(
|
|
Commands.AutoHideEmptyTracks,
|
|
FExecuteAction::CreateSP(this, &STimingView::ToggleAutoHideEmptyTracks),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &STimingView::IsAutoHideEmptyTracksEnabled));
|
|
|
|
CommandList->MapAction(
|
|
Commands.PanningOnScreenEdges,
|
|
FExecuteAction::CreateSP(this, &STimingView::TogglePanningOnScreenEdges),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &STimingView::IsPanningOnScreenEdgesEnabled));
|
|
|
|
CommandList->MapAction(
|
|
Commands.ToggleCompactMode,
|
|
FExecuteAction::CreateSP(this, &STimingView::ToggleCompactMode),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &STimingView::IsCompactModeEnabled));
|
|
|
|
CommandList->MapAction(
|
|
Commands.ShowMainGraphTrack,
|
|
FExecuteAction::CreateSP(this, &STimingView::ShowHideGraphTrack_Execute),
|
|
//FCanExecuteAction(),
|
|
FCanExecuteAction::CreateLambda([] { return true; }),
|
|
FIsActionChecked::CreateSP(this, &STimingView::ShowHideGraphTrack_IsChecked));
|
|
|
|
CommandList->MapAction(
|
|
Commands.QuickFind,
|
|
FExecuteAction::CreateSP(this, &STimingView::QuickFind_Execute),
|
|
FCanExecuteAction::CreateSP(this, &STimingView::QuickFind_CanExecute));
|
|
|
|
FrameSharedState->BindCommands();
|
|
ThreadTimingSharedState->BindCommands();
|
|
LoadingSharedState->BindCommands();
|
|
FileActivitySharedState->BindCommands();
|
|
TimingRegionsSharedState->BindCommands();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::SetAutoScroll(bool bOnOff)
|
|
{
|
|
bAutoScroll = bOnOff;
|
|
|
|
// Persistent option. Save it to the config file.
|
|
FInsightsSettings& Settings = FInsightsManager::Get()->GetSettings();
|
|
Settings.SetAndSaveAutoScroll(bAutoScroll);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::AutoScroll_OnCheckStateChanged(ECheckBoxState NewRadioState)
|
|
{
|
|
SetAutoScroll(NewRadioState == ECheckBoxState::Checked);
|
|
Viewport.AddDirtyFlags(ETimingTrackViewportDirtyFlags::HInvalidated);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
ECheckBoxState STimingView::AutoScroll_IsChecked() const
|
|
{
|
|
return bAutoScroll ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::SetAutoScrollFrameAlignment(int32 FrameType)
|
|
{
|
|
AutoScrollFrameAlignment = FrameType;
|
|
|
|
// Persistent option. Save it to the config file.
|
|
FInsightsSettings& Settings = FInsightsManager::Get()->GetSettings();
|
|
Settings.SetAndSaveAutoScrollFrameAlignment(FrameType);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool STimingView::CompareAutoScrollFrameAlignment(int32 FrameType) const
|
|
{
|
|
return AutoScrollFrameAlignment == FrameType;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::SetAutoScrollViewportOffset(double Percent)
|
|
{
|
|
AutoScrollViewportOffsetPercent = Percent;
|
|
|
|
// Persistent option. Save it to the config file.
|
|
FInsightsSettings& Settings = FInsightsManager::Get()->GetSettings();
|
|
Settings.SetAndSaveAutoScrollViewportOffsetPercent(Percent);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool STimingView::CompareAutoScrollViewportOffset(double Percent) const
|
|
{
|
|
return AutoScrollViewportOffsetPercent == Percent;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::SetAutoScrollDelay(double Delay)
|
|
{
|
|
AutoScrollMinDelay = Delay;
|
|
|
|
// Persistent option. Save it to the config file.
|
|
FInsightsSettings& Settings = FInsightsManager::Get()->GetSettings();
|
|
Settings.SetAndSaveAutoScrollMinDelay(Delay);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool STimingView::CompareAutoScrollDelay(double Delay) const
|
|
{
|
|
return AutoScrollMinDelay == Delay;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
double STimingView::EnforceHorizontalScrollLimits(const double InStartTime)
|
|
{
|
|
double NewStartTime = InStartTime;
|
|
|
|
double MinT, MaxT;
|
|
Viewport.GetHorizontalScrollLimits(MinT, MaxT);
|
|
|
|
if (NewStartTime > MaxT)
|
|
{
|
|
NewStartTime = MaxT;
|
|
OverscrollRight = 1.0f;
|
|
}
|
|
|
|
if (NewStartTime < MinT)
|
|
{
|
|
NewStartTime = MinT;
|
|
OverscrollLeft = 1.0f;
|
|
}
|
|
|
|
return NewStartTime;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
float STimingView::EnforceVerticalScrollLimits(const float InScrollPosY)
|
|
{
|
|
float NewScrollPosY = InScrollPosY;
|
|
|
|
const float DY = Viewport.GetScrollHeight() - Viewport.GetScrollableAreaHeight();
|
|
const float MinY = FMath::Min(DY, 0.0f);
|
|
const float MaxY = DY - MinY;
|
|
|
|
if (NewScrollPosY > MaxY)
|
|
{
|
|
NewScrollPosY = MaxY;
|
|
OverscrollBottom = 1.0f;
|
|
}
|
|
|
|
if (NewScrollPosY < MinY)
|
|
{
|
|
NewScrollPosY = MinY;
|
|
OverscrollTop = 1.0f;
|
|
}
|
|
|
|
return NewScrollPosY;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::HorizontalScrollBar_OnUserScrolled(float ScrollOffset)
|
|
{
|
|
// Disable auto-scroll if user starts scrolling with horizontal scrollbar.
|
|
SetAutoScroll(false);
|
|
|
|
Viewport.OnUserScrolled(HorizontalScrollBar, ScrollOffset);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::UpdateHorizontalScrollBar()
|
|
{
|
|
Viewport.UpdateScrollBar(HorizontalScrollBar);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::VerticalScrollBar_OnUserScrolled(float ScrollOffset)
|
|
{
|
|
Viewport.OnUserScrolledY(VerticalScrollBar, ScrollOffset);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::UpdateVerticalScrollBar()
|
|
{
|
|
Viewport.UpdateScrollBarY(VerticalScrollBar);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::ScrollAtPosY(float ScrollPosY)
|
|
{
|
|
if (ScrollPosY != Viewport.GetScrollPosY())
|
|
{
|
|
Viewport.SetScrollPosY(ScrollPosY);
|
|
UpdateVerticalScrollBar();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::BringIntoViewY(float InTopScrollY, float InBottomScrollY)
|
|
{
|
|
const float ScrollY = Viewport.GetScrollPosY();
|
|
const float ScrollH = Viewport.GetScrollableAreaHeight();
|
|
if (ScrollY > InTopScrollY)
|
|
{
|
|
ScrollAtPosY(InTopScrollY);
|
|
}
|
|
else if (ScrollY + ScrollH < InBottomScrollY)
|
|
{
|
|
ScrollAtPosY(FMath::Min(InTopScrollY, InBottomScrollY - ScrollH));
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::BringScrollableTrackIntoView(const FBaseTimingTrack& InTrack)
|
|
{
|
|
if (ensure(InTrack.GetLocation() == ETimingTrackLocation::Scrollable))
|
|
{
|
|
const float TopScrollY = InTrack.GetPosY() + Viewport.GetScrollPosY() - Viewport.GetTopOffset() - Viewport.GetPosY();
|
|
BringIntoViewY(TopScrollY, TopScrollY + InTrack.GetHeight());
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::ScrollAtTime(double StartTime)
|
|
{
|
|
if (Viewport.ScrollAtTime(StartTime))
|
|
{
|
|
UpdateHorizontalScrollBar();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::CenterOnTimeInterval(double IntervalStartTime, double IntervalDuration)
|
|
{
|
|
if (Viewport.CenterOnTimeInterval(IntervalStartTime, IntervalDuration))
|
|
{
|
|
Viewport.EnforceHorizontalScrollLimits(1.0); // 1.0 is to disable interpolation
|
|
UpdateHorizontalScrollBar();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::ZoomOnTimeInterval(double IntervalStartTime, double IntervalDuration)
|
|
{
|
|
if (Viewport.ZoomOnTimeInterval(IntervalStartTime, IntervalDuration))
|
|
{
|
|
Viewport.EnforceHorizontalScrollLimits(1.0); // 1.0 is to disable interpolation
|
|
UpdateHorizontalScrollBar();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::BringIntoView(double StartTime, double EndTime)
|
|
{
|
|
EndTime = Viewport.RestrictEndTime(EndTime);
|
|
|
|
// Increase interval with 8% (of view size) on each side.
|
|
const double DT = Viewport.GetDuration() * 0.08;
|
|
StartTime -= DT;
|
|
EndTime += DT;
|
|
|
|
double NewStartTime = Viewport.GetStartTime();
|
|
|
|
if (EndTime > Viewport.GetEndTime())
|
|
{
|
|
NewStartTime += EndTime - Viewport.GetEndTime();
|
|
}
|
|
|
|
if (StartTime < NewStartTime)
|
|
{
|
|
NewStartTime = StartTime;
|
|
}
|
|
|
|
ScrollAtTime(NewStartTime);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::SelectTimeInterval(double IntervalStartTime, double IntervalDuration)
|
|
{
|
|
SelectionStartTime = IntervalStartTime;
|
|
SelectionEndTime = IntervalStartTime + IntervalDuration;
|
|
|
|
if (GetFrameTypeToSnapTo() != ETraceFrameType::TraceFrameType_Count && IsInTimingProfiler())
|
|
{
|
|
SnapToFrameBound(SelectionStartTime, SelectionEndTime);
|
|
}
|
|
|
|
LastSelectionType = ESelectionType::TimeRange;
|
|
RaiseSelectionChanged();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::SnapToFrameBound(double& StartTime, double& EndTime)
|
|
{
|
|
TSharedPtr<const TraceServices::IAnalysisSession> Session = FInsightsManager::Get()->GetSession();
|
|
|
|
if (!Session.IsValid())
|
|
{
|
|
return;
|
|
}
|
|
|
|
TraceServices::FAnalysisSessionReadScope SessionReadScope(*Session.Get());
|
|
const TraceServices::IFrameProvider& FramesProvider = TraceServices::ReadFrameProvider(*Session.Get());
|
|
|
|
ETraceFrameType FrameTypeToSnapTo = GetFrameTypeToSnapTo();
|
|
uint32 FrameNum = FramesProvider.GetFrameNumberForTimestamp(FrameTypeToSnapTo, StartTime);
|
|
const TraceServices::FFrame* StartFrame = FramesProvider.GetFrame(FrameTypeToSnapTo, FrameNum);
|
|
|
|
if (StartFrame == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (StartFrame->EndTime < StartTime)
|
|
{
|
|
if (FrameNum + 1 < FramesProvider.GetFrameCount(FrameTypeToSnapTo))
|
|
{
|
|
StartFrame = FramesProvider.GetFrame(FrameTypeToSnapTo, FrameNum + 1);
|
|
}
|
|
}
|
|
|
|
FrameNum = FramesProvider.GetFrameNumberForTimestamp(FrameTypeToSnapTo, EndTime);
|
|
const TraceServices::FFrame* EndFrame = FramesProvider.GetFrame(FrameTypeToSnapTo, FrameNum);
|
|
|
|
if (EndFrame == nullptr)
|
|
{
|
|
EndFrame = FramesProvider.GetFrame(FrameTypeToSnapTo, FrameNum - 1);
|
|
if (EndFrame == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// If the interval is before the first frame or after the last frame.
|
|
if (StartFrame->StartTime > EndTime || EndFrame->EndTime < StartTime)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// If the interval is between frames.
|
|
if (EndFrame->Index < StartFrame->Index)
|
|
{
|
|
return;
|
|
}
|
|
|
|
StartTime = StartFrame->StartTime;
|
|
EndTime = FMath::Min(EndFrame->EndTime, Session->GetDurationSeconds());
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::RaiseSelectionChanging()
|
|
{
|
|
OnSelectionChangedDelegate.Broadcast(Timing::ETimeChangedFlags::Interactive, SelectionStartTime, SelectionEndTime);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::RaiseSelectionChanged()
|
|
{
|
|
OnSelectionChangedDelegate.Broadcast(Timing::ETimeChangedFlags::None, SelectionStartTime, SelectionEndTime);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::RaiseTimeMarkerChanging(TSharedRef<FTimeMarker> InTimeMarker)
|
|
{
|
|
if (InTimeMarker == DefaultTimeMarker)
|
|
{
|
|
const double Time = DefaultTimeMarker->GetTime();
|
|
OnTimeMarkerChangedDelegate.Broadcast(Timing::ETimeChangedFlags::Interactive, Time);
|
|
}
|
|
OnCustomTimeMarkerChangedDelegate.Broadcast(Timing::ETimeChangedFlags::Interactive, InTimeMarker);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::RaiseTimeMarkerChanged(TSharedRef<FTimeMarker> InTimeMarker)
|
|
{
|
|
if (InTimeMarker == DefaultTimeMarker)
|
|
{
|
|
const double Time = DefaultTimeMarker->GetTime();
|
|
OnTimeMarkerChangedDelegate.Broadcast(Timing::ETimeChangedFlags::None, Time);
|
|
}
|
|
OnCustomTimeMarkerChangedDelegate.Broadcast(Timing::ETimeChangedFlags::None, InTimeMarker);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
double STimingView::GetTimeMarker() const
|
|
{
|
|
return DefaultTimeMarker->GetTime();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::SetTimeMarker(double InMarkerTime)
|
|
{
|
|
DefaultTimeMarker->SetTime(InMarkerTime);
|
|
RaiseTimeMarkerChanged(DefaultTimeMarker);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::AddOverlayWidget(const TSharedRef<SWidget>& InWidget)
|
|
{
|
|
if (ExtensionOverlay.IsValid())
|
|
{
|
|
ExtensionOverlay->AddSlot()
|
|
[
|
|
InWidget
|
|
];
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::SetAndCenterOnTimeMarker(double Time)
|
|
{
|
|
SetTimeMarker(Time);
|
|
|
|
double MinT, MaxT;
|
|
Viewport.GetHorizontalScrollLimits(MinT, MaxT);
|
|
const double ViewportDuration = Viewport.GetDuration();
|
|
MinT += ViewportDuration / 2;
|
|
MaxT += ViewportDuration / 2;
|
|
Time = FMath::Clamp<double>(Time, MinT, MaxT);
|
|
|
|
Time = Viewport.AlignTimeToPixel(Time);
|
|
CenterOnTimeInterval(Time, 0.0);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::SelectToTimeMarker(double Time)
|
|
{
|
|
const double TimeMarker = GetTimeMarker();
|
|
if (TimeMarker < Time)
|
|
{
|
|
SelectTimeInterval(TimeMarker, Time - TimeMarker);
|
|
}
|
|
else
|
|
{
|
|
SelectTimeInterval(Time, TimeMarker - Time);
|
|
}
|
|
|
|
SetTimeMarker(Time);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::SetTimeMarkersVisible(bool bIsMarkersTrackVisible)
|
|
{
|
|
if (MarkersTrack->IsVisible() != bIsMarkersTrackVisible)
|
|
{
|
|
MarkersTrack->SetVisibilityFlag(bIsMarkersTrackVisible);
|
|
|
|
if (MarkersTrack->IsVisible())
|
|
{
|
|
if (Viewport.GetScrollPosY() != 0.0f)
|
|
{
|
|
UE_LOG(LogTimingProfiler, Log, TEXT("SetTimeMarkersVisible!!!"));
|
|
Viewport.SetScrollPosY(Viewport.GetScrollPosY() + MarkersTrack->GetHeight());
|
|
}
|
|
|
|
MarkersTrack->SetDirtyFlag();
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogTimingProfiler, Log, TEXT("SetTimeMarkersVisible!!!"));
|
|
Viewport.SetScrollPosY(Viewport.GetScrollPosY() - MarkersTrack->GetHeight());
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::SetDrawOnlyBookmarks(bool bIsBookmarksTrack)
|
|
{
|
|
if (MarkersTrack->IsBookmarksTrack() != bIsBookmarksTrack)
|
|
{
|
|
const float PrevHeight = MarkersTrack->GetHeight();
|
|
MarkersTrack->SetBookmarksTrackFlag(bIsBookmarksTrack);
|
|
|
|
if (MarkersTrack->IsVisible())
|
|
{
|
|
if (Viewport.GetScrollPosY() != 0.0f)
|
|
{
|
|
UE_LOG(LogTimingProfiler, Log, TEXT("SetDrawOnlyBookmarks!!!"));
|
|
Viewport.SetScrollPosY(Viewport.GetScrollPosY() + MarkersTrack->GetHeight() - PrevHeight);
|
|
}
|
|
|
|
MarkersTrack->SetDirtyFlag();
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
const TSharedPtr<FBaseTimingTrack> STimingView::GetTrackAt(float InPosX, float InPosY) const
|
|
{
|
|
TSharedPtr<FBaseTimingTrack> FoundTrack;
|
|
|
|
if (InPosY < Viewport.GetPosY())
|
|
{
|
|
// above viewport
|
|
}
|
|
else if (InPosY < Viewport.GetPosY() + Viewport.GetTopOffset())
|
|
{
|
|
// Top Docked Tracks
|
|
for (const TSharedPtr<FBaseTimingTrack>& TrackPtr : TopDockedTracks)
|
|
{
|
|
const FBaseTimingTrack& Track = *TrackPtr;
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
if (InPosY >= Track.GetPosY() && InPosY < Track.GetPosY() + Track.GetHeight())
|
|
{
|
|
FoundTrack = TrackPtr;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (InPosY < Viewport.GetPosY() + Viewport.GetHeight() - Viewport.GetBottomOffset())
|
|
{
|
|
// Scrollable Tracks
|
|
for (const TSharedPtr<FBaseTimingTrack>& TrackPtr : ScrollableTracks)
|
|
{
|
|
const FBaseTimingTrack& Track = *TrackPtr;
|
|
if (Track.IsVisible())
|
|
{
|
|
if (InPosY >= Track.GetPosY() && InPosY < Track.GetPosY() + Track.GetHeight())
|
|
{
|
|
FoundTrack = TrackPtr;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (InPosY < Viewport.GetPosY() + Viewport.GetHeight())
|
|
{
|
|
// Bottom Docked Tracks
|
|
for (const TSharedPtr<FBaseTimingTrack>& TrackPtr : BottomDockedTracks)
|
|
{
|
|
const FBaseTimingTrack& Track = *TrackPtr;
|
|
if (TrackPtr->IsVisible())
|
|
{
|
|
if (InPosY >= Track.GetPosY() && InPosY < Track.GetPosY() + Track.GetHeight())
|
|
{
|
|
FoundTrack = TrackPtr;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// below viewport
|
|
}
|
|
|
|
return FoundTrack;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
static bool EventBelongsToTrack(const ITimingEvent& Event, const FBaseTimingTrack* Track)
|
|
{
|
|
if (Track == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (const TSharedRef<FBaseTimingTrack>& ChildTrack : Track->GetChildTracks())
|
|
{
|
|
if (EventBelongsToTrack(Event, &*ChildTrack))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return &Event.GetTrack().Get() == Track;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::UpdateHoveredTimingEvent(float InMousePosX, float InMousePosY)
|
|
{
|
|
TSharedPtr<FBaseTimingTrack> NewHoveredTrack = GetTrackAt(InMousePosX, InMousePosY);
|
|
if (NewHoveredTrack != HoveredTrack)
|
|
{
|
|
HoveredTrack = NewHoveredTrack;
|
|
OnHoveredTrackChangedDelegate.Broadcast(HoveredTrack);
|
|
}
|
|
|
|
TSharedPtr<const ITimingEvent> NewHoveredEvent;
|
|
if (HoveredTrack.IsValid())
|
|
{
|
|
FStopwatch Stopwatch;
|
|
Stopwatch.Start();
|
|
|
|
NewHoveredEvent = HoveredTrack->GetEvent(InMousePosX, InMousePosY, Viewport);
|
|
|
|
Stopwatch.Stop();
|
|
const double DT = Stopwatch.GetAccumulatedTime();
|
|
if (DT > 0.001)
|
|
{
|
|
UE_LOG(LogTimingProfiler, Log, TEXT("HoveredTrack [%g, %g] GetEvent: %.1f ms"), InMousePosX, InMousePosY, DT * 1000.0);
|
|
}
|
|
}
|
|
|
|
if (NewHoveredEvent.IsValid())
|
|
{
|
|
if (!HoveredEvent.IsValid() || !NewHoveredEvent->Equals(*HoveredEvent))
|
|
{
|
|
FStopwatch Stopwatch;
|
|
Stopwatch.Start();
|
|
|
|
HoveredEvent = NewHoveredEvent;
|
|
ensure(EventBelongsToTrack(*HoveredEvent, HoveredTrack.Get()));
|
|
HoveredTrack->UpdateEventStats(const_cast<ITimingEvent&>(*HoveredEvent));
|
|
|
|
Stopwatch.Update();
|
|
const double T1 = Stopwatch.GetAccumulatedTime();
|
|
|
|
HoveredTrack->InitTooltip(Tooltip, *HoveredEvent);
|
|
|
|
Stopwatch.Update();
|
|
const double T2 = Stopwatch.GetAccumulatedTime();
|
|
|
|
OnHoveredEventChangedDelegate.Broadcast(HoveredEvent);
|
|
|
|
Stopwatch.Update();
|
|
const double T3 = Stopwatch.GetAccumulatedTime();
|
|
if (T3 > 0.001)
|
|
{
|
|
UE_LOG(LogTimingProfiler, Log, TEXT("HoveredTrack [%g, %g] Tooltip: %.1f ms (%.1f + %.1f + %.1f)"),
|
|
InMousePosX, InMousePosY, T3 * 1000.0, T1 * 1000.0, (T2 - T1) * 1000.0, (T3 - T2) * 1000.0);
|
|
}
|
|
}
|
|
Tooltip.SetDesiredOpacity(1.0f);
|
|
}
|
|
else
|
|
{
|
|
if (HoveredEvent.IsValid())
|
|
{
|
|
HoveredEvent.Reset();
|
|
OnHoveredEventChangedDelegate.Broadcast(HoveredEvent);
|
|
}
|
|
Tooltip.SetDesiredOpacity(0.0f);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::OnSelectedTimingEventChanged()
|
|
{
|
|
if (SelectedEvent.IsValid())
|
|
{
|
|
SelectedEvent->GetTrack()->UpdateEventStats(const_cast<ITimingEvent&>(*SelectedEvent));
|
|
SelectedEvent->GetTrack()->OnEventSelected(*SelectedEvent);
|
|
}
|
|
|
|
OnSelectedEventChangedDelegate.Broadcast(SelectedEvent);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::SelectHoveredTimingTrack()
|
|
{
|
|
SelectTimingTrack(HoveredTrack, false);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::SelectHoveredTimingEvent()
|
|
{
|
|
SelectTimingEvent(HoveredEvent, true, false);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::SelectTimingTrack(const TSharedPtr<FBaseTimingTrack> InTrack, bool bBringTrackIntoView)
|
|
{
|
|
if (SelectedTrack != InTrack)
|
|
{
|
|
SelectedTrack = InTrack;
|
|
|
|
if (SelectedTrack.IsValid())
|
|
{
|
|
if (bBringTrackIntoView &&
|
|
SelectedTrack->GetLocation() == ETimingTrackLocation::Scrollable)
|
|
{
|
|
TSharedPtr<FBaseTimingTrack> ParentTrack = SelectedTrack->GetParentTrack().Pin();
|
|
if (ParentTrack.IsValid())
|
|
{
|
|
BringScrollableTrackIntoView(*ParentTrack);
|
|
}
|
|
else
|
|
{
|
|
BringScrollableTrackIntoView(*SelectedTrack);
|
|
}
|
|
}
|
|
}
|
|
|
|
OnSelectedTrackChangedDelegate.Broadcast(SelectedTrack);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::SelectTimingEvent(const TSharedPtr<const ITimingEvent> InEvent, bool bBringEventIntoViewHorizontally, bool bBringEventIntoViewVertically)
|
|
{
|
|
if (SelectedEvent != InEvent)
|
|
{
|
|
SelectedEvent = InEvent;
|
|
|
|
if (SelectedEvent.IsValid())
|
|
{
|
|
LastSelectionType = ESelectionType::TimingEvent;
|
|
if (bBringEventIntoViewHorizontally)
|
|
{
|
|
BringIntoView(SelectedEvent->GetStartTime(), SelectedEvent->GetEndTime());
|
|
}
|
|
if (bBringEventIntoViewVertically)
|
|
{
|
|
bBringSelectedEventIntoViewVerticallyOnNextTick = true;
|
|
// We need the layout to be calculated in one frame, no animations, otherwise the event might not be in view at the end of the animation.
|
|
Viewport.AddDirtyFlags(ETimingTrackViewportDirtyFlags::VLayoutChanged);
|
|
}
|
|
}
|
|
|
|
OnSelectedTimingEventChanged();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::ToggleGraphSeries(const TSharedPtr<const ITimingEvent> InEvent)
|
|
{
|
|
if (InEvent.Get() && InEvent.Get()->Is<FThreadTrackEvent>() && IsInTimingProfiler())
|
|
{
|
|
const FThreadTrackEvent& TrackEvent = InEvent.Get()->As<FThreadTrackEvent>();
|
|
|
|
using namespace UE::Insights::TimingProfiler;
|
|
FTimingProfilerManager::Get()->ToggleTimingViewMainGraphEventSeries(TrackEvent.GetTimerId());
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::SelectLeftTimingEvent()
|
|
{
|
|
if (SelectedEvent.IsValid())
|
|
{
|
|
const uint32 Depth = SelectedEvent->GetDepth();
|
|
const double StartTime = SelectedEvent->GetStartTime();
|
|
const double EndTime = SelectedEvent->GetEndTime();
|
|
|
|
auto EventFilter = [Depth, StartTime, EndTime](double EventStartTime, double EventEndTime, uint32 EventDepth)
|
|
{
|
|
return EventDepth == Depth
|
|
&& (EventStartTime < StartTime || EventEndTime < EndTime);
|
|
};
|
|
|
|
const TSharedPtr<const ITimingEvent> LeftEvent = SelectedEvent->GetTrack()->SearchEvent(
|
|
FTimingEventSearchParameters(0.0, StartTime, ETimingEventSearchFlags::SearchAll, EventFilter));
|
|
|
|
if (LeftEvent.IsValid())
|
|
{
|
|
SelectedEvent = LeftEvent;
|
|
BringIntoView(SelectedEvent->GetStartTime(), SelectedEvent->GetEndTime());
|
|
OnSelectedTimingEventChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::SelectRightTimingEvent()
|
|
{
|
|
if (SelectedEvent.IsValid())
|
|
{
|
|
const uint32 Depth = SelectedEvent->GetDepth();
|
|
const double StartTime = SelectedEvent->GetStartTime();
|
|
const double EndTime = SelectedEvent->GetEndTime();
|
|
|
|
auto EventFilter = [Depth, StartTime, EndTime](double EventStartTime, double EventEndTime, uint32 EventDepth)
|
|
{
|
|
return EventDepth == Depth
|
|
&& (EventStartTime > StartTime || EventEndTime > EndTime);
|
|
};
|
|
|
|
const TSharedPtr<const ITimingEvent> RightEvent = SelectedEvent->GetTrack()->SearchEvent(
|
|
FTimingEventSearchParameters(EndTime, Viewport.GetMaxValidTime(), ETimingEventSearchFlags::StopAtFirstMatch, EventFilter));
|
|
|
|
if (RightEvent.IsValid())
|
|
{
|
|
SelectedEvent = RightEvent;
|
|
BringIntoView(SelectedEvent->GetStartTime(), SelectedEvent->GetEndTime());
|
|
OnSelectedTimingEventChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::SelectUpTimingEvent()
|
|
{
|
|
if (SelectedEvent.IsValid() &&
|
|
SelectedEvent->GetDepth() > 0)
|
|
{
|
|
const uint32 Depth = SelectedEvent->GetDepth() - 1;
|
|
const double StartTime = SelectedEvent->GetStartTime();
|
|
const double EndTime = SelectedEvent->GetEndTime();
|
|
|
|
auto EventFilter = [Depth, StartTime, EndTime](double EventStartTime, double EventEndTime, uint32 EventDepth)
|
|
{
|
|
return EventDepth == Depth
|
|
&& EventStartTime <= EndTime
|
|
&& EventEndTime >= StartTime;
|
|
};
|
|
|
|
const TSharedPtr<const ITimingEvent> UpEvent = SelectedEvent->GetTrack()->SearchEvent(
|
|
FTimingEventSearchParameters(StartTime, EndTime, ETimingEventSearchFlags::StopAtFirstMatch, EventFilter));
|
|
|
|
if (UpEvent.IsValid())
|
|
{
|
|
SelectedEvent = UpEvent;
|
|
BringIntoView(SelectedEvent->GetStartTime(), SelectedEvent->GetEndTime());
|
|
OnSelectedTimingEventChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::SelectDownTimingEvent()
|
|
{
|
|
if (SelectedEvent.IsValid())
|
|
{
|
|
const uint32 Depth = SelectedEvent->GetDepth() + 1;
|
|
const double StartTime = SelectedEvent->GetStartTime();
|
|
const double EndTime = SelectedEvent->GetEndTime();
|
|
double LargestDuration = 0.0;
|
|
|
|
auto EventFilter = [Depth, StartTime, EndTime, &LargestDuration](double EventStartTime, double EventEndTime, uint32 EventDepth)
|
|
{
|
|
const double Duration = EventEndTime - EventStartTime;
|
|
return Duration > LargestDuration
|
|
&& EventDepth == Depth
|
|
&& EventStartTime <= EndTime
|
|
&& EventEndTime >= StartTime;
|
|
};
|
|
|
|
auto EventMatched = [&LargestDuration](double EventStartTime, double EventEndTime, uint32 EventDepth)
|
|
{
|
|
const double Duration = EventEndTime - EventStartTime;
|
|
LargestDuration = Duration;
|
|
};
|
|
|
|
const TSharedPtr<const ITimingEvent> DownEvent = SelectedEvent->GetTrack()->SearchEvent(
|
|
FTimingEventSearchParameters(StartTime, EndTime, ETimingEventSearchFlags::SearchAll, EventFilter, EventMatched));
|
|
|
|
if (DownEvent.IsValid())
|
|
{
|
|
SelectedEvent = DownEvent;
|
|
BringIntoView(SelectedEvent->GetStartTime(), SelectedEvent->GetEndTime());
|
|
OnSelectedTimingEventChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::FrameSelection()
|
|
{
|
|
double StartTime, EndTime;
|
|
|
|
ESelectionType Type = ESelectionType::None;
|
|
|
|
if (LastSelectionType == ESelectionType::TimingEvent)
|
|
{
|
|
// Try framing the selected timing event.
|
|
if (SelectedEvent.IsValid())
|
|
{
|
|
Type = ESelectionType::TimingEvent;
|
|
}
|
|
|
|
// Next time, try framing the selected time range.
|
|
LastSelectionType = ESelectionType::TimeRange;
|
|
}
|
|
else if (LastSelectionType == ESelectionType::TimeRange)
|
|
{
|
|
// Try framing the selected time range.
|
|
if (SelectionEndTime > SelectionStartTime)
|
|
{
|
|
Type = ESelectionType::TimeRange;
|
|
}
|
|
|
|
// Next time, try framing the selected timing event.
|
|
LastSelectionType = ESelectionType::TimingEvent;
|
|
}
|
|
|
|
// If no last selection or last selection is empty...
|
|
if (LastSelectionType == ESelectionType::None || Type == ESelectionType::None)
|
|
{
|
|
// First, try framing the selected timing event...
|
|
if (SelectedEvent.IsValid())
|
|
{
|
|
Type = ESelectionType::TimingEvent;
|
|
}
|
|
else // ...otherwise, try framing the selected time range
|
|
{
|
|
Type = ESelectionType::TimeRange;
|
|
}
|
|
}
|
|
|
|
if (Type == ESelectionType::TimingEvent)
|
|
{
|
|
// Frame the selected event.
|
|
StartTime = SelectedEvent->GetStartTime();
|
|
EndTime = Viewport.RestrictEndTime(SelectedEvent->GetEndTime());
|
|
if (EndTime == StartTime)
|
|
{
|
|
EndTime += 1.0 / Viewport.GetScaleX(); // +1px
|
|
}
|
|
if (SelectedEvent->GetTrack()->GetLocation() == ETimingTrackLocation::Scrollable)
|
|
{
|
|
BringScrollableTrackIntoView(*SelectedEvent->GetTrack());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Frame the selected time range.
|
|
StartTime = SelectionStartTime;
|
|
EndTime = Viewport.RestrictEndTime(SelectionEndTime);
|
|
}
|
|
|
|
if (EndTime > StartTime)
|
|
{
|
|
const double Duration = EndTime - StartTime;
|
|
if (Viewport.ZoomOnTimeInterval(StartTime - Duration * 0.1, Duration * 1.2))
|
|
{
|
|
UpdateHorizontalScrollBar();
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::SetEventFilter(const TSharedPtr<ITimingEventFilter> InEventFilter)
|
|
{
|
|
TimingEventFilter = InEventFilter;
|
|
Viewport.AddDirtyFlags(ETimingTrackViewportDirtyFlags::HInvalidated);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::ToggleEventFilterByEventType(const uint64 EventType)
|
|
{
|
|
if (IsFilterByEventType(EventType))
|
|
{
|
|
SetEventFilter(nullptr); // reset filter
|
|
}
|
|
else
|
|
{
|
|
LLM_SCOPE_BYTAG(Insights);
|
|
TSharedRef<FTimingEventFilterByEventType> NewEventFilter = MakeShared<FTimingEventFilterByEventType>(EventType);
|
|
NewEventFilter->SetFilterByTrackTypeName(true);
|
|
NewEventFilter->SetTrackTypeName(FThreadTimingTrack::GetStaticTypeName());
|
|
SetEventFilter(NewEventFilter); // set new filter
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool STimingView::IsFilterByEventType(const uint64 EventType) const
|
|
{
|
|
if (TimingEventFilter.IsValid() &&
|
|
TimingEventFilter->Is<FTimingEventFilterByEventType>())
|
|
{
|
|
const FTimingEventFilterByEventType& EventFilterByEventType = TimingEventFilter->As<FTimingEventFilterByEventType>();
|
|
return EventFilterByEventType.GetEventType() == EventType;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::CreateCompactMenuLine(FMenuBuilder& MenuBuilder, FText Label, TSharedRef<SWidget> InnerWidget) const
|
|
{
|
|
TSharedRef<SWidget> Widget =
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.Padding(FMargin(-8.0f, 0.0f, 0.0f, 0.0f))
|
|
.AutoWidth()
|
|
.HAlign(HAlign_Fill)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SBox)
|
|
.WidthOverride(140.0f)
|
|
.HAlign(HAlign_Right)
|
|
.Padding(FMargin(0.0f, 0.0f, 4.0f, 0.0f))
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(Label)
|
|
]
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.Padding(FMargin(0.0f, 1.0f, 8.0f, 1.0f))
|
|
.HAlign(HAlign_Fill)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
InnerWidget
|
|
]
|
|
;
|
|
|
|
MenuBuilder.AddWidget(Widget, FText(), true);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
TSharedRef<SWidget> STimingView::MakeCompactAutoScrollOptionsMenu()
|
|
{
|
|
FMenuBuilder MenuBuilder(/*bInShouldCloseWindowAfterMenuSelection=*/true, CommandList);
|
|
|
|
MenuBuilder.BeginSection("AutoScrollOptions", LOCTEXT("CompactAutoScrollOptionsMenu_Section", "Auto-Scroll Options"));
|
|
{
|
|
CreateCompactMenuLine(MenuBuilder,
|
|
LOCTEXT("FrameAlignment", "Frame Alignment:"),
|
|
SNew(SSegmentedControl<int32>)
|
|
.OnValueChanged_Lambda([this](int32 InValue) { SetAutoScrollFrameAlignment(InValue); })
|
|
.Value_Lambda([this] { return AutoScrollFrameAlignment; })
|
|
+ SSegmentedControl<int32>::Slot(-1)
|
|
.Text(LOCTEXT("None", "None"))
|
|
.ToolTip(LOCTEXT("AutoScrollNoFrameAlignment_Tooltip", "Disables the frame alignment (when auto-scrolling)."))
|
|
+ SSegmentedControl<int32>::Slot((int32)TraceFrameType_Game)
|
|
.Text(LOCTEXT("Game", "Game"))
|
|
.ToolTip(LOCTEXT("AutoScrollNoFrameAlignment_Tooltip", "Disables the frame alignment (when auto-scrolling)."))
|
|
+ SSegmentedControl<int32>::Slot((int32)TraceFrameType_Rendering)
|
|
.Text(LOCTEXT("Rendering", "Rendering"))
|
|
.ToolTip(LOCTEXT("AutoScrollAlignWithGameFrames_Tooltip", "Aligns the viewport's center position with the start time of a Game frame (when auto-scrolling)."))
|
|
);
|
|
|
|
CreateCompactMenuLine(MenuBuilder,
|
|
LOCTEXT("ViewportOffset", "Viewport Offset:"),
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
SNew(SEditableTextBox)
|
|
.MinDesiredWidth(40.0f)
|
|
.HintText(LOCTEXT("ViewportOffsetCustomHint", "Custom"))
|
|
.Text_Lambda([this]
|
|
{
|
|
const FString ValueStr = (AutoScrollViewportOffsetPercent == 0.0) ? FString(TEXT("0")) : FString::Printf(TEXT("%g%%"), AutoScrollViewportOffsetPercent * 100.0);
|
|
return FText::FromString(ValueStr);
|
|
})
|
|
.OnTextChanged_Lambda([this](const FText& InText)
|
|
{
|
|
double OffsetPercent = FCString::Atof(*InText.ToString());
|
|
SetAutoScrollViewportOffset(OffsetPercent * 0.01);
|
|
})
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.FillWidth(1.0f)
|
|
[
|
|
SNew(SSegmentedControl<double>)
|
|
.OnValueChanged_Lambda([this](double InValue) { SetAutoScrollViewportOffset(InValue); })
|
|
.Value_Lambda([this] { return AutoScrollViewportOffsetPercent; })
|
|
+ SSegmentedControl<double>::Slot(-0.1)
|
|
.Text(LOCTEXT("AutoScrollViewportOffset-10", "-10%"))
|
|
.ToolTip(LOCTEXT("AutoScrollViewportOffset-10_Tooltip", "Sets the viewport offset to -10% (i.e. backward) of the viewport's width (when auto-scrolling).\nAvoids flickering as the end of the session will be outside of the viewport."))
|
|
+ SSegmentedControl<double>::Slot(0.0)
|
|
.Text(LOCTEXT("AutoScrollViewportOffset0", "0"))
|
|
.ToolTip(LOCTEXT("AutoScrollViewportOffset0_Tooltip", "Sets the viewport offset to 0 (when auto-scrolling).\nThe right side of the viewport will correspond to the current session time."))
|
|
+ SSegmentedControl<double>::Slot(+0.1)
|
|
.Text(LOCTEXT("AutoScrollViewportOffset+10", "+10%"))
|
|
.ToolTip(LOCTEXT("AutoScrollViewportOffset+10_Tooltip", "Sets the viewport offset to +10% (i.e. forward) of the viewport's width (when auto-scrolling).\nAllows 10% empty space on the right side of the viewport."))
|
|
]
|
|
);
|
|
|
|
CreateCompactMenuLine(MenuBuilder,
|
|
LOCTEXT("Delay", "Delay:"),
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
SNew(SEditableTextBox)
|
|
.MinDesiredWidth(40.0f)
|
|
.HintText(LOCTEXT("AutoScrollDelayCustomHint", "Custom"))
|
|
.Text_Lambda([this]
|
|
{
|
|
const FString ValueStr = (AutoScrollMinDelay == 0.0) ? FString(TEXT("0")) : FString::Printf(TEXT("%gs"), AutoScrollMinDelay);
|
|
return FText::FromString(ValueStr);
|
|
})
|
|
.OnTextChanged_Lambda([this](const FText& InText)
|
|
{
|
|
double Delay = FCString::Atof(*InText.ToString());
|
|
SetAutoScrollDelay(Delay);
|
|
})
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.FillWidth(1.0f)
|
|
[
|
|
SNew(SSegmentedControl<double>)
|
|
.OnValueChanged_Lambda([this](double InValue) { SetAutoScrollDelay(InValue); })
|
|
.Value_Lambda([this] { return AutoScrollMinDelay; })
|
|
+ SSegmentedControl<double>::Slot(0.0)
|
|
.Text(LOCTEXT("AutoScrollDelay0", "0"))
|
|
.ToolTip(LOCTEXT("AutoScrollDelay0_Tooltip", "Sets the time delay of the auto-scroll update to 0."))
|
|
+ SSegmentedControl<double>::Slot(0.3)
|
|
.Text(LOCTEXT("AutoScrollDelay300ms", "300ms"))
|
|
.ToolTip(LOCTEXT("AutoScrollDelay300ms_Tooltip", "Sets the time delay of the auto-scroll update to 300ms."))
|
|
+ SSegmentedControl<double>::Slot(1.0)
|
|
.Text(LOCTEXT("AutoScrollDelay1s", "1s"))
|
|
.ToolTip(LOCTEXT("AutoScrollDelay1s_Tooltip", "Sets the time delay of the auto-scroll update to 1s."))
|
|
+ SSegmentedControl<double>::Slot(3.0)
|
|
.Text(LOCTEXT("AutoScrollDelay3s", "3s"))
|
|
.ToolTip(LOCTEXT("AutoScrollDelay3s_Tooltip", "Sets the time delay of the auto-scroll update to 3s."))
|
|
]
|
|
);
|
|
}
|
|
MenuBuilder.EndSection();
|
|
|
|
return MenuBuilder.MakeWidget();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
TSharedRef<SWidget> STimingView::MakeAutoScrollOptionsMenu()
|
|
{
|
|
FMenuBuilder MenuBuilder(/*bInShouldCloseWindowAfterMenuSelection=*/true, CommandList);
|
|
MenuBuilder.SetSearchable(false);
|
|
|
|
MenuBuilder.BeginSection("Alignment", LOCTEXT("AutoScrollOptionsMenu_Section_Alignment", "Alignment"));
|
|
{
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("AutoScrollNoFrameAlignment", "None"),
|
|
LOCTEXT("AutoScrollNoFrameAlignment_Tooltip", "Disables the frame alignment (when auto-scrolling)."),
|
|
FSlateIcon(),
|
|
FUIAction(FExecuteAction::CreateSP(this, &STimingView::SetAutoScrollFrameAlignment, -1),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &STimingView::CompareAutoScrollFrameAlignment, -1)),
|
|
NAME_None,
|
|
EUserInterfaceActionType::RadioButton
|
|
);
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("AutoScrollAlignWithGameFrames", "Game Frames"),
|
|
LOCTEXT("AutoScrollAlignWithGameFrames_Tooltip", "Aligns the viewport's center position with the start time of a Game frame (when auto-scrolling)."),
|
|
FSlateIcon(),
|
|
FUIAction(FExecuteAction::CreateSP(this, &STimingView::SetAutoScrollFrameAlignment, (int32)TraceFrameType_Game),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &STimingView::CompareAutoScrollFrameAlignment, (int32)TraceFrameType_Game)),
|
|
NAME_None,
|
|
EUserInterfaceActionType::RadioButton
|
|
);
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("AutoScrollAlignWithRenderingFrames", "Rendering Frames"),
|
|
LOCTEXT("AutoScrollAlignWithRenderingFrames_Tooltip", "Aligns the viewport's center position with the start time of a Rendering frame (when auto-scrolling)."),
|
|
FSlateIcon(),
|
|
FUIAction(FExecuteAction::CreateSP(this, &STimingView::SetAutoScrollFrameAlignment, (int32)TraceFrameType_Rendering),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &STimingView::CompareAutoScrollFrameAlignment, (int32)TraceFrameType_Rendering)),
|
|
NAME_None,
|
|
EUserInterfaceActionType::RadioButton
|
|
);
|
|
}
|
|
MenuBuilder.EndSection();
|
|
|
|
MenuBuilder.BeginSection("ViewportOffset", LOCTEXT("AutoScrollOptionsMenu_Section_ViewportOffset", "Viewport Offset"));
|
|
{
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("AutoScrollViewportOffset-10", "-10%"),
|
|
LOCTEXT("AutoScrollViewportOffset-10_Tooltip", "Sets the viewport offset to -10% (i.e. backward) of the viewport's width (when auto-scrolling).\nAvoids flickering as the end of the session will be outside of the viewport."),
|
|
FSlateIcon(),
|
|
FUIAction(FExecuteAction::CreateSP(this, &STimingView::SetAutoScrollViewportOffset, -0.1),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &STimingView::CompareAutoScrollViewportOffset, -0.1)),
|
|
NAME_None,
|
|
EUserInterfaceActionType::RadioButton
|
|
);
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("AutoScrollViewportOffset0", "0"),
|
|
LOCTEXT("AutoScrollViewportOffset0_Tooltip", "Sets the viewport offset to 0 (when auto-scrolling).\nThe right side of the viewport will correspond to the current session time."),
|
|
FSlateIcon(),
|
|
FUIAction(FExecuteAction::CreateSP(this, &STimingView::SetAutoScrollViewportOffset, 0.0),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &STimingView::CompareAutoScrollViewportOffset, 0.0)),
|
|
NAME_None,
|
|
EUserInterfaceActionType::RadioButton
|
|
);
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("AutoScrollViewportOffset+10", "+10%"),
|
|
LOCTEXT("AutoScrollViewportOffset+10_Tooltip", "Sets the viewport offset to +10% (i.e. forward) of the viewport's width (when auto-scrolling).\nAllows 10% empty space on the right side of the viewport."),
|
|
FSlateIcon(),
|
|
FUIAction(FExecuteAction::CreateSP(this, &STimingView::SetAutoScrollViewportOffset, +0.1),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &STimingView::CompareAutoScrollViewportOffset, +0.1)),
|
|
NAME_None,
|
|
EUserInterfaceActionType::RadioButton
|
|
);
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
FUIAction(FExecuteAction(), FCanExecuteAction(), FIsActionChecked::CreateLambda(
|
|
[this]() -> bool
|
|
{
|
|
return AutoScrollViewportOffsetPercent != 0.0 &&
|
|
AutoScrollViewportOffsetPercent != -0.1 &&
|
|
AutoScrollViewportOffsetPercent != +0.1;
|
|
})),
|
|
SNew(SBox)
|
|
.Padding(FMargin(0.0f, 0.0f, 12.0f, 0.0f))
|
|
[
|
|
SNew(SEditableTextBox)
|
|
.HintText(LOCTEXT("ViewportOffsetCustomHint", "Custom"))
|
|
.Text_Lambda([this]
|
|
{
|
|
const FString ValueStr = (AutoScrollViewportOffsetPercent == 0.0) ? FString(TEXT("0")) : FString::Printf(TEXT("%g%%"), AutoScrollViewportOffsetPercent * 100.0);
|
|
return FText::FromString(ValueStr);
|
|
})
|
|
.OnTextChanged_Lambda([this](const FText& InText)
|
|
{
|
|
double OffsetPercent = FCString::Atof(*InText.ToString());
|
|
SetAutoScrollViewportOffset(OffsetPercent * 0.01);
|
|
})
|
|
],
|
|
NAME_None,
|
|
LOCTEXT("AutoScrollViewportOffsetCustom_Tooltip", "Sets a custom value for the viewport offset as percent from viewport's width (when auto-scrolling)."),
|
|
EUserInterfaceActionType::RadioButton
|
|
);
|
|
}
|
|
MenuBuilder.EndSection();
|
|
|
|
MenuBuilder.BeginSection("Delay", LOCTEXT("AutoScrollOptionsMenu_Section_Delay", "Delay"));
|
|
{
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("AutoScrollDelay0", "0"),
|
|
LOCTEXT("AutoScrollDelay0_Tooltip", "Sets the time delay of the auto-scroll update to 0."),
|
|
FSlateIcon(),
|
|
FUIAction(FExecuteAction::CreateSP(this, &STimingView::SetAutoScrollDelay, 0.0),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &STimingView::CompareAutoScrollDelay, 0.0)),
|
|
NAME_None,
|
|
EUserInterfaceActionType::RadioButton
|
|
);
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("AutoScrollDelay300ms", "300ms"),
|
|
LOCTEXT("AutoScrollDelay300ms_Tooltip", "Sets the time delay of the auto-scroll update to 300ms."),
|
|
FSlateIcon(),
|
|
FUIAction(FExecuteAction::CreateSP(this, &STimingView::SetAutoScrollDelay, 0.3),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &STimingView::CompareAutoScrollDelay, 0.3)),
|
|
NAME_None,
|
|
EUserInterfaceActionType::RadioButton
|
|
);
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("AutoScrollDelay1s", "1s"),
|
|
LOCTEXT("AutoScrollDelay1s_Tooltip", "Sets the time delay of the auto-scroll update to 1s."),
|
|
FSlateIcon(),
|
|
FUIAction(FExecuteAction::CreateSP(this, &STimingView::SetAutoScrollDelay, 1.0),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &STimingView::CompareAutoScrollDelay, 1.0)),
|
|
NAME_None,
|
|
EUserInterfaceActionType::RadioButton
|
|
);
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("AutoScrollDelay3s", "3s"),
|
|
LOCTEXT("AutoScrollDelay3s_Tooltip", "Sets the time delay of the auto-scroll update to 3s."),
|
|
FSlateIcon(),
|
|
FUIAction(FExecuteAction::CreateSP(this, &STimingView::SetAutoScrollDelay, 3.0),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &STimingView::CompareAutoScrollDelay, 3.0)),
|
|
NAME_None,
|
|
EUserInterfaceActionType::RadioButton
|
|
);
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
FUIAction(FExecuteAction(), FCanExecuteAction(), FIsActionChecked::CreateLambda(
|
|
[this]() -> bool
|
|
{
|
|
return AutoScrollMinDelay != 0.0 &&
|
|
AutoScrollMinDelay != 0.3 &&
|
|
AutoScrollMinDelay != 1.0 &&
|
|
AutoScrollMinDelay != 3.0;
|
|
})),
|
|
SNew(SBox)
|
|
.Padding(FMargin(0.0f, 0.0f, 12.0f, 0.0f))
|
|
[
|
|
SNew(SEditableTextBox)
|
|
.HintText(LOCTEXT("AutoScrollDelayCustomHint", "Custom"))
|
|
.Text_Lambda([this]
|
|
{
|
|
const FString ValueStr = (AutoScrollMinDelay == 0.0) ? FString(TEXT("0")) : FString::Printf(TEXT("%gs"), AutoScrollMinDelay);
|
|
return FText::FromString(ValueStr);
|
|
})
|
|
.OnTextChanged_Lambda([this](const FText& InText)
|
|
{
|
|
double Delay = FCString::Atof(*InText.ToString());
|
|
SetAutoScrollDelay(Delay);
|
|
})
|
|
],
|
|
NAME_None,
|
|
LOCTEXT("AutoScrollDelayCustom_Tooltip", "Sets a custom time delay (in seconds) for the auto-scroll update."),
|
|
EUserInterfaceActionType::RadioButton
|
|
);
|
|
}
|
|
MenuBuilder.EndSection();
|
|
|
|
return MenuBuilder.MakeWidget();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
TSharedRef<SWidget> STimingView::MakeAllTracksMenu()
|
|
{
|
|
FMenuBuilder MenuBuilder(/*bInShouldCloseWindowAfterMenuSelection=*/true, CommandList);
|
|
MenuBuilder.SetSearchable(false);
|
|
|
|
CreateAllTracksMenu(MenuBuilder);
|
|
|
|
return MenuBuilder.MakeWidget();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::CreateAllTracksMenu(FMenuBuilder& MenuBuilder)
|
|
{
|
|
constexpr float MaxDesiredHeight = 24.0f + 8 * 18.0f;
|
|
constexpr float MaxDesiredHeightScrollableTracks = 24.0f + 16 * 18.0f;
|
|
constexpr float MinDesiredWidth = 300.0f;
|
|
constexpr float MaxDesiredWidth = 300.0f;
|
|
|
|
if (TopDockedTracks.Num() > 0)
|
|
{
|
|
MenuBuilder.BeginSection("TopDockedTracks", LOCTEXT("ContextMenu_Section_TopDockedTracks", "Top Docked Tracks"));
|
|
//MenuBuilder.AddWidget(
|
|
// SNew(STextBlock)
|
|
// .Text(LOCTEXT("TopDockedTracks", "Top Docked Tracks")),
|
|
// FText(), true);
|
|
|
|
MenuBuilder.AddWidget(
|
|
SNew(SBox)
|
|
.MaxDesiredHeight(MaxDesiredHeight)
|
|
.MinDesiredWidth(MinDesiredWidth)
|
|
.MaxDesiredWidth(MaxDesiredWidth)
|
|
[
|
|
SNew(STimingViewTrackList, SharedThis(this), ETimingTrackLocation::TopDocked)
|
|
],
|
|
FText(), true);
|
|
|
|
MenuBuilder.EndSection();
|
|
}
|
|
|
|
if (ScrollableTracks.Num() > 0)
|
|
{
|
|
MenuBuilder.BeginSection("ScrollableTracks", LOCTEXT("ContextMenu_Section_ScrollableTracks", "Scrollable Tracks"));
|
|
//MenuBuilder.AddWidget(
|
|
// SNew(STextBlock)
|
|
// .Text(LOCTEXT("ScrollableTracks", "Scrollable Tracks")),
|
|
// FText(), true);
|
|
|
|
MenuBuilder.AddWidget(
|
|
SNew(SBox)
|
|
.Padding(FMargin(0.0f, 0.0f))
|
|
.MaxDesiredHeight(MaxDesiredHeightScrollableTracks)
|
|
.MinDesiredWidth(MinDesiredWidth)
|
|
.MaxDesiredWidth(MaxDesiredWidth)
|
|
[
|
|
SNew(STimingViewTrackList, SharedThis(this), ETimingTrackLocation::Scrollable)
|
|
],
|
|
FText(), true);
|
|
|
|
MenuBuilder.EndSection();
|
|
}
|
|
|
|
if (BottomDockedTracks.Num() > 0)
|
|
{
|
|
MenuBuilder.BeginSection("BottomDockedTracks", LOCTEXT("ContextMenu_Section_BottomDockedTracks", "Bottom Docked Tracks"));
|
|
//MenuBuilder.AddWidget(
|
|
// SNew(STextBlock)
|
|
// .Text(LOCTEXT("BottomDockedTracks", "Bottom Docked Tracks")),
|
|
// FText(), true);
|
|
|
|
MenuBuilder.AddWidget(
|
|
SNew(SBox)
|
|
.MaxDesiredHeight(MaxDesiredHeight)
|
|
.MinDesiredWidth(MinDesiredWidth)
|
|
.MaxDesiredWidth(MaxDesiredWidth)
|
|
[
|
|
SNew(STimingViewTrackList, SharedThis(this), ETimingTrackLocation::BottomDocked)
|
|
],
|
|
FText(), true);
|
|
|
|
MenuBuilder.EndSection();
|
|
}
|
|
|
|
if (ForegroundTracks.Num() > 0)
|
|
{
|
|
MenuBuilder.BeginSection("ForegroundTracks", LOCTEXT("ContextMenu_Section_ForegroundTracks", "Foreground Tracks"));
|
|
//MenuBuilder.AddWidget(
|
|
// SNew(STextBlock)
|
|
// .Text(LOCTEXT("ForegroundTracks", "Foreground Tracks")),
|
|
// FText(), true);
|
|
|
|
MenuBuilder.AddWidget(
|
|
SNew(SBox)
|
|
.MaxDesiredHeight(MaxDesiredHeight)
|
|
.MinDesiredWidth(MinDesiredWidth)
|
|
.MaxDesiredWidth(MaxDesiredWidth)
|
|
[
|
|
SNew(STimingViewTrackList, SharedThis(this), ETimingTrackLocation::Foreground)
|
|
],
|
|
FText(), true);
|
|
|
|
MenuBuilder.EndSection();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
TSharedRef<SWidget> STimingView::MakeCpuGpuTracksFilterMenu()
|
|
{
|
|
FMenuBuilder MenuBuilder(/*bInShouldCloseWindowAfterMenuSelection=*/true, CommandList);
|
|
|
|
// Let any plugin extend the GPU Tracks Filter menu.
|
|
for (Timing::ITimingViewExtender* Extender : GetExtenders())
|
|
{
|
|
Extender->ExtendGpuTracksFilterMenu(*this, MenuBuilder);
|
|
}
|
|
#if UE_INSIGHTS_BACKWARD_COMPATIBILITY_UE54
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
::Insights::ITimingViewSession* CurrentTimingViewSession = (::Insights::ITimingViewSession*)(Timing::ITimingViewSession*)this;
|
|
for (::Insights::ITimingViewExtender* Extender : GetOldExtenders())
|
|
{
|
|
Extender->ExtendGpuTracksFilterMenu(*CurrentTimingViewSession, MenuBuilder);
|
|
}
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
#endif // UE_INSIGHTS_BACKWARD_COMPATIBILITY_UE54
|
|
|
|
// Let any plugin extend the CPU Tracks Filter menu.
|
|
for (Timing::ITimingViewExtender* Extender : GetExtenders())
|
|
{
|
|
Extender->ExtendCpuTracksFilterMenu(*this, MenuBuilder);
|
|
}
|
|
#if UE_INSIGHTS_BACKWARD_COMPATIBILITY_UE54
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
for (::Insights::ITimingViewExtender* Extender : GetOldExtenders())
|
|
{
|
|
Extender->ExtendCpuTracksFilterMenu(*CurrentTimingViewSession, MenuBuilder);
|
|
}
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
#endif // UE_INSIGHTS_BACKWARD_COMPATIBILITY_UE54
|
|
|
|
return MenuBuilder.MakeWidget();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
TSharedRef<SWidget> STimingView::MakeOtherTracksFilterMenu()
|
|
{
|
|
FMenuBuilder MenuBuilder(/*bInShouldCloseWindowAfterMenuSelection=*/true, CommandList);
|
|
|
|
const FTimingViewCommands& Commands = FTimingViewCommands::Get();
|
|
|
|
MenuBuilder.BeginSection("GraphTracks", LOCTEXT("ContextMenu_Section_GraphTracks", "Main Graph Track"));
|
|
{
|
|
MenuBuilder.AddMenuEntry(Commands.ShowMainGraphTrack);
|
|
}
|
|
MenuBuilder.EndSection();
|
|
|
|
// Let any plugin extend the Other Tracks Filter menu.
|
|
for (Timing::ITimingViewExtender* Extender : GetExtenders())
|
|
{
|
|
Extender->ExtendOtherTracksFilterMenu(*this, MenuBuilder);
|
|
}
|
|
#if UE_INSIGHTS_BACKWARD_COMPATIBILITY_UE54
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
::Insights::ITimingViewSession* CurrentTimingViewSession = (::Insights::ITimingViewSession*)(Timing::ITimingViewSession*)this;
|
|
for (::Insights::ITimingViewExtender* Extender : GetOldExtenders())
|
|
{
|
|
Extender->ExtendOtherTracksFilterMenu(*CurrentTimingViewSession, MenuBuilder);
|
|
}
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
#endif // UE_INSIGHTS_BACKWARD_COMPATIBILITY_UE54
|
|
|
|
return MenuBuilder.MakeWidget();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool STimingView::ShowHideGraphTrack_IsChecked() const
|
|
{
|
|
return GraphTrack->IsVisible();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::ShowHideGraphTrack_Execute()
|
|
{
|
|
GraphTrack->ToggleVisibility();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
TSharedRef<SWidget> STimingView::MakePluginTracksFilterMenu()
|
|
{
|
|
FMenuBuilder MenuBuilder(/*bInShouldCloseWindowAfterMenuSelection=*/true, CommandList);
|
|
|
|
// Let any plugin extend the filter menu.
|
|
for (Timing::ITimingViewExtender* Extender : GetExtenders())
|
|
{
|
|
Extender->ExtendFilterMenu(*this, MenuBuilder);
|
|
}
|
|
#if UE_INSIGHTS_BACKWARD_COMPATIBILITY_UE54
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
::Insights::ITimingViewSession* CurrentTimingViewSession = (::Insights::ITimingViewSession*)(Timing::ITimingViewSession*)this;
|
|
for (::Insights::ITimingViewExtender* Extender : GetOldExtenders())
|
|
{
|
|
Extender->ExtendFilterMenu(*CurrentTimingViewSession, MenuBuilder);
|
|
}
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
#endif // UE_INSIGHTS_BACKWARD_COMPATIBILITY_UE54
|
|
|
|
return MenuBuilder.MakeWidget();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
TSharedRef<SWidget> STimingView::MakeViewModeMenu()
|
|
{
|
|
FMenuBuilder MenuBuilder(/*bInShouldCloseWindowAfterMenuSelection=*/true, CommandList);
|
|
|
|
const FTimingViewCommands& Commands = FTimingViewCommands::Get();
|
|
|
|
MenuBuilder.BeginSection("ViewMode", LOCTEXT("ContextMenu_Section_ViewMode", "View Mode"));
|
|
{
|
|
MenuBuilder.AddMenuEntry(Commands.ToggleCompactMode);
|
|
MenuBuilder.AddMenuEntry(Commands.AutoHideEmptyTracks);
|
|
}
|
|
MenuBuilder.EndSection();
|
|
|
|
CreateDepthLimitMenu(MenuBuilder);
|
|
|
|
CreateCpuThreadTrackColoringModeMenu(MenuBuilder);
|
|
|
|
MenuBuilder.BeginSection("Misc", LOCTEXT("ContextMenu_Section_Misc", "Misc Settings"));
|
|
{
|
|
MenuBuilder.AddMenuEntry(Commands.PanningOnScreenEdges);
|
|
}
|
|
MenuBuilder.EndSection();
|
|
|
|
return MenuBuilder.MakeWidget();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool STimingView::IsCompactModeEnabled() const
|
|
{
|
|
return Viewport.IsLayoutCompactModeEnabled();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::ToggleCompactMode()
|
|
{
|
|
Viewport.SwitchLayoutCompactMode();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool STimingView::IsAutoHideEmptyTracksEnabled() const
|
|
{
|
|
return (Viewport.GetLayout().TargetMinTimelineH == 0.0f);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::ToggleAutoHideEmptyTracks()
|
|
{
|
|
Viewport.ToggleLayoutMinTrackHeight();
|
|
|
|
for (const TSharedPtr<FBaseTimingTrack>& TrackPtr : ScrollableTracks)
|
|
{
|
|
if (TrackPtr->Is<FTimingEventsTrack>())
|
|
{
|
|
TrackPtr->SetHeight(0.0f);
|
|
}
|
|
}
|
|
|
|
// Persistent option. Save it to the config file.
|
|
FInsightsSettings& Settings = FInsightsManager::Get()->GetSettings();
|
|
const bool bIsEnabled = IsAutoHideEmptyTracksEnabled();
|
|
Settings.SetAndSaveAutoHideEmptyTracks(bIsEnabled);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool STimingView::IsPanningOnScreenEdgesEnabled() const
|
|
{
|
|
return bAllowPanningOnScreenEdges;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::TogglePanningOnScreenEdges()
|
|
{
|
|
bAllowPanningOnScreenEdges = !bAllowPanningOnScreenEdges;
|
|
|
|
// Persistent option. Save it to the config file.
|
|
FInsightsSettings& Settings = FInsightsManager::Get()->GetSettings();
|
|
Settings.SetAndSavePanningOnScreenEdges(bAllowPanningOnScreenEdges);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::CreateDepthLimitMenu(FMenuBuilder& MenuBuilder)
|
|
{
|
|
MenuBuilder.BeginSection("DepthLimit", LOCTEXT("ContextMenu_Section_DepthLimit", "Depth Limit"));
|
|
{
|
|
// Note: We use the custom FInsightsMenuBuilder::AddMenuEntry in order to set the same key binding text for multiple menu items.
|
|
|
|
FInsightsMenuBuilder::AddMenuEntry(MenuBuilder,
|
|
FUIAction(
|
|
FExecuteAction::CreateSP(this, &STimingView::SetEventDepthLimit, FTimingProfilerManager::UnlimitedEventDepth),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &STimingView::CheckEventDepthLimit, FTimingProfilerManager::UnlimitedEventDepth)),
|
|
LOCTEXT("UnlimitedDepth", "Unlimited"),
|
|
LOCTEXT("UnlimitedDepth_Desc", "Timing Events tracks (like the CPU Thread tracks) can have unlimited depth (lanes)."),
|
|
TAttribute<FText>::Create(TAttribute<FText>::FGetter::CreateSP(this, &STimingView::GetEventDepthLimitKeybindingText, FTimingProfilerManager::UnlimitedEventDepth)),
|
|
EUserInterfaceActionType::RadioButton);
|
|
|
|
FInsightsMenuBuilder::AddMenuEntry(MenuBuilder,
|
|
FUIAction(
|
|
FExecuteAction::CreateSP(this, &STimingView::SetEventDepthLimit, (uint32)4),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &STimingView::CheckEventDepthLimit, (uint32)4)),
|
|
LOCTEXT("DepthLimit4", "4 Lanes"),
|
|
LOCTEXT("DepthLimit4_Desc", "Timing Events tracks (like the CPU Thread tracks) can have maximum 4 lanes."),
|
|
TAttribute<FText>::Create(TAttribute<FText>::FGetter::CreateSP(this, &STimingView::GetEventDepthLimitKeybindingText, (uint32)4)),
|
|
EUserInterfaceActionType::RadioButton);
|
|
|
|
FInsightsMenuBuilder::AddMenuEntry(MenuBuilder,
|
|
FUIAction(
|
|
FExecuteAction::CreateSP(this, &STimingView::SetEventDepthLimit, (uint32)1),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &STimingView::CheckEventDepthLimit, (uint32)1)),
|
|
LOCTEXT("DepthLimit1", "Single Lane"),
|
|
LOCTEXT("DepthLimit1_Desc", "Timing Events tracks (like the CPU Thread tracks) can have a single lane."),
|
|
TAttribute<FText>::Create(TAttribute<FText>::FGetter::CreateSP(this, &STimingView::GetEventDepthLimitKeybindingText, (uint32)1)),
|
|
EUserInterfaceActionType::RadioButton);
|
|
}
|
|
MenuBuilder.EndSection();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
FText STimingView::GetEventDepthLimitKeybindingText(uint32 DepthLimit) const
|
|
{
|
|
uint32 CurrentDepthLimit = FTimingProfilerManager::Get()->GetEventDepthLimit();
|
|
uint32 NextDepthLimit = GetNextEventDepthLimit(CurrentDepthLimit);
|
|
if (DepthLimit == NextDepthLimit)
|
|
{
|
|
return FInputChord(EKeys::X).GetInputText().ToUpper();
|
|
}
|
|
return FText::GetEmpty();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
uint32 STimingView::GetNextEventDepthLimit(uint32 DepthLimit) const
|
|
{
|
|
if (DepthLimit == 1)
|
|
{
|
|
return 4;
|
|
}
|
|
else if (DepthLimit == 4)
|
|
{
|
|
return FTimingProfilerManager::UnlimitedEventDepth;
|
|
}
|
|
else // Unlimited
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::ChooseNextEventDepthLimit()
|
|
{
|
|
const uint32 CurrentDepthLimit = FTimingProfilerManager::Get()->GetEventDepthLimit();
|
|
const uint32 NextDepthLimit = GetNextEventDepthLimit(CurrentDepthLimit);
|
|
FTimingProfilerManager::Get()->SetEventDepthLimit(NextDepthLimit);
|
|
Viewport.AddDirtyFlags(ETimingTrackViewportDirtyFlags::HInvalidated);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::SetEventDepthLimit(uint32 DepthLimit)
|
|
{
|
|
FTimingProfilerManager::Get()->SetEventDepthLimit(DepthLimit);
|
|
Viewport.AddDirtyFlags(ETimingTrackViewportDirtyFlags::HInvalidated);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool STimingView::CheckEventDepthLimit(uint32 DepthLimit) const
|
|
{
|
|
return DepthLimit == FTimingProfilerManager::Get()->GetEventDepthLimit();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::CreateCpuThreadTrackColoringModeMenu(FMenuBuilder& MenuBuilder)
|
|
{
|
|
MenuBuilder.BeginSection("CpuThreadTrackColoringMode", LOCTEXT("ContextMenu_Section_CpuThreadTrackColoringMode", "Coloring Mode (CPU Thread Tracks)"));
|
|
{
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("CpuThreadTrackColoringMode_ByTimerName", "By Timer Name"),
|
|
LOCTEXT("CpuThreadTrackColoringMode_ByTimerName_Desc", "Assign a color to CPU/GPU timing events based on their timer name."),
|
|
FSlateIcon(),
|
|
FUIAction(
|
|
FExecuteAction::CreateSP(this, &STimingView::SetCpuThreadTrackColoringMode, ETimingEventsColoringMode::ByTimerName),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &STimingView::CheckCpuThreadTrackColoringMode, ETimingEventsColoringMode::ByTimerName)),
|
|
NAME_None,
|
|
EUserInterfaceActionType::RadioButton
|
|
);
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("CpuThreadTrackColoringMode_ByTimerId", "By Timer Id"),
|
|
LOCTEXT("CpuThreadTrackColoringMode_ByTimerId_Desc", "Assign a color to CPU/GPU timing events based on their timer id."),
|
|
FSlateIcon(),
|
|
FUIAction(
|
|
FExecuteAction::CreateSP(this, &STimingView::SetCpuThreadTrackColoringMode, ETimingEventsColoringMode::ByTimerId),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &STimingView::CheckCpuThreadTrackColoringMode, ETimingEventsColoringMode::ByTimerId)),
|
|
NAME_None,
|
|
EUserInterfaceActionType::RadioButton
|
|
);
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("CpuThreadTrackColoringMode_BySourceFile", "By Source File"),
|
|
LOCTEXT("CpuThreadTrackColoringMode_BySourceFile_Desc", "Assign a color to CPU/GPU timing events based on their source file."),
|
|
FSlateIcon(),
|
|
FUIAction(
|
|
FExecuteAction::CreateSP(this, &STimingView::SetCpuThreadTrackColoringMode, ETimingEventsColoringMode::BySourceFile),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &STimingView::CheckCpuThreadTrackColoringMode, ETimingEventsColoringMode::BySourceFile)),
|
|
NAME_None,
|
|
EUserInterfaceActionType::RadioButton
|
|
);
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("CpuThreadTrackColoringMode_ByDuration", "By Duration"),
|
|
LOCTEXT("CpuThreadTrackColoringMode_ByDuration_Desc", "Assign a color to CPU/GPU timing events based on their duration (inclusive time).\n\t≥ 10ms : red\n\t≥ 1ms : yellow\n\t≥ 100μs : green\n\t≥ 10μs : cyan\n\t≥ 1μs : blue\n\t< 1μs : gray"),
|
|
FSlateIcon(),
|
|
FUIAction(
|
|
FExecuteAction::CreateSP(this, &STimingView::SetCpuThreadTrackColoringMode, ETimingEventsColoringMode::ByDuration),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &STimingView::CheckCpuThreadTrackColoringMode, ETimingEventsColoringMode::ByDuration)),
|
|
NAME_None,
|
|
EUserInterfaceActionType::RadioButton
|
|
);
|
|
}
|
|
MenuBuilder.EndSection();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::ChooseNextCpuThreadTrackColoringMode()
|
|
{
|
|
uint32 Mode = (uint32)FTimingProfilerManager::Get()->GetColoringMode();
|
|
Mode = (Mode + 1) % (uint32)ETimingEventsColoringMode::Count;
|
|
FTimingProfilerManager::Get()->SetColoringMode((ETimingEventsColoringMode)Mode);
|
|
Viewport.AddDirtyFlags(ETimingTrackViewportDirtyFlags::HInvalidated);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::SetCpuThreadTrackColoringMode(UE::Insights::ETimingEventsColoringMode Mode)
|
|
{
|
|
FTimingProfilerManager::Get()->SetColoringMode(Mode);
|
|
Viewport.AddDirtyFlags(ETimingTrackViewportDirtyFlags::HInvalidated);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool STimingView::CheckCpuThreadTrackColoringMode(UE::Insights::ETimingEventsColoringMode Mode)
|
|
{
|
|
return Mode == FTimingProfilerManager::Get()->GetColoringMode();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool STimingView::ToggleTrackVisibility_IsChecked(uint64 InTrackId) const
|
|
{
|
|
const TSharedPtr<FBaseTimingTrack>* const TrackPtrPtr = AllTracks.Find(InTrackId);
|
|
if (TrackPtrPtr)
|
|
{
|
|
return (*TrackPtrPtr)->IsVisible();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::ToggleTrackVisibility_Execute(uint64 InTrackId)
|
|
{
|
|
const TSharedPtr<FBaseTimingTrack>* TrackPtrPtr = AllTracks.Find(InTrackId);
|
|
if (TrackPtrPtr)
|
|
{
|
|
(*TrackPtrPtr)->ToggleVisibility();
|
|
HandleTrackVisibilityChanged();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool STimingView::QuickFind_CanExecute() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::QuickFind_Execute()
|
|
{
|
|
using namespace UE::Insights;
|
|
|
|
LLM_SCOPE_BYTAG(Insights);
|
|
|
|
if (!QuickFindVm.IsValid())
|
|
{
|
|
TSharedPtr<FFilterConfigurator> NewFilterConfigurator = MakeShared<FFilterConfigurator>();
|
|
|
|
NewFilterConfigurator->Add(MakeShared<FFilter>(
|
|
static_cast<int32>(EFilterField::StartTime),
|
|
LOCTEXT("StartTime", "Start Time"),
|
|
LOCTEXT("StartTime", "Start Time"),
|
|
EFilterDataType::Double,
|
|
MakeShared<FTimeFilterValueConverter>(),
|
|
FFilterService::Get()->GetDoubleOperators()));
|
|
|
|
NewFilterConfigurator->Add(MakeShared<FFilter>(
|
|
static_cast<int32>(EFilterField::EndTime),
|
|
LOCTEXT("EndTime", "End Time"),
|
|
LOCTEXT("EndTime", "End Time"),
|
|
EFilterDataType::Double,
|
|
MakeShared<FTimeFilterValueConverter>(),
|
|
FFilterService::Get()->GetDoubleOperators()));
|
|
|
|
NewFilterConfigurator->Add(MakeShared<FFilter>(
|
|
static_cast<int32>(EFilterField::Duration),
|
|
LOCTEXT("Duration", "Duration"),
|
|
LOCTEXT("Duration", "Duration"),
|
|
EFilterDataType::Double,
|
|
MakeShared<FTimeFilterValueConverter>(),
|
|
FFilterService::Get()->GetDoubleOperators()));
|
|
|
|
TSharedRef<FFilterWithSuggestions> TrackFilter = MakeShared<FFilterWithSuggestions>(
|
|
static_cast<int32>(EFilterField::TrackName),
|
|
LOCTEXT("Track", "Track"),
|
|
LOCTEXT("Track", "Track"),
|
|
EFilterDataType::String,
|
|
nullptr,
|
|
FFilterService::Get()->GetStringOperators());
|
|
TrackFilter->SetCallback([this](const FString& Text, TArray<FString>& OutSuggestions)
|
|
{
|
|
this->PopulateTrackSuggestionList(Text, OutSuggestions);
|
|
});
|
|
NewFilterConfigurator->Add(TrackFilter);
|
|
|
|
NewFilterConfigurator->Add(MakeShared<FFilter>(
|
|
static_cast<int32>(EFilterField::TimerId),
|
|
LOCTEXT("TimerId", "Timer Id"),
|
|
LOCTEXT("TimerId", "Timer Id"),
|
|
EFilterDataType::Int64,
|
|
nullptr,
|
|
FFilterService::Get()->GetIntegerOperators()));
|
|
|
|
NewFilterConfigurator->Add(MakeShared<FTimerNameFilter>());
|
|
NewFilterConfigurator->Add(MakeShared<FMetadataFilter>());
|
|
|
|
for (Timing::ITimingViewExtender* Extender : GetExtenders())
|
|
{
|
|
Extender->AddQuickFindFilters(NewFilterConfigurator);
|
|
}
|
|
#if UE_INSIGHTS_BACKWARD_COMPATIBILITY_UE54
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
for (::Insights::ITimingViewExtender* Extender : GetOldExtenders())
|
|
{
|
|
Extender->AddQuickFindFilters(NewFilterConfigurator);
|
|
}
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
#endif // UE_INSIGHTS_BACKWARD_COMPATIBILITY_UE54
|
|
|
|
QuickFindVm = MakeShared<::Insights::FQuickFind>(NewFilterConfigurator);
|
|
QuickFindVm->GetOnFindFirstEvent().AddSP(this, &STimingView::FindFirstEvent);
|
|
QuickFindVm->GetOnFindPreviousEvent().AddSP(this, &STimingView::FindPrevEvent);
|
|
QuickFindVm->GetOnFindNextEvent().AddSP(this, &STimingView::FindNextEvent);
|
|
QuickFindVm->GetOnFindLastEvent().AddSP(this, &STimingView::FindLastEvent);
|
|
QuickFindVm->GetOnFilterAllEvent().AddSP(this, &STimingView::FilterAllTracks);
|
|
QuickFindVm->GetOnClearFiltersEvent().AddSP(this, &STimingView::ClearFilters);
|
|
}
|
|
|
|
SAssignNew(QuickFindWidgetSharedPtr, ::Insights::SQuickFind, QuickFindVm);
|
|
|
|
if (FGlobalTabmanager::Get()->HasTabSpawner(QuickFindTabId))
|
|
{
|
|
FGlobalTabmanager::Get()->TryInvokeTab(QuickFindTabId);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
TSharedRef<SDockTab> STimingView::SpawnQuickFindTab(const FSpawnTabArgs& Args)
|
|
{
|
|
const TSharedRef<SDockTab> DockTab = SNew(SDockTab)
|
|
.TabRole(ETabRole::NomadTab);
|
|
|
|
const TSharedPtr<SWindow>& OwnerWindow = Args.GetOwnerWindow();
|
|
if (OwnerWindow.IsValid() && OwnerWindow != FSlateApplication::Get().FindWidgetWindow(SharedThis(this)))
|
|
{
|
|
TSharedPtr<SWindow> TopmostAncestor = OwnerWindow->GetTopmostAncestor();
|
|
if (TopmostAncestor != OwnerWindow)
|
|
{
|
|
const FVector2D DPIProbePoint = TopmostAncestor->GetPositionInScreen() + FVector2D(10.0, 10.0);
|
|
const float LocalDPIScaleFactor = FPlatformApplicationMisc::GetDPIScaleFactorAtPoint(static_cast<float>(DPIProbePoint.X), static_cast<float>(DPIProbePoint.Y));
|
|
OwnerWindow->Resize(FVector2D(600 * LocalDPIScaleFactor, 400 * LocalDPIScaleFactor));
|
|
}
|
|
}
|
|
|
|
if (!QuickFindWidgetSharedPtr.IsValid())
|
|
{
|
|
return DockTab;
|
|
}
|
|
|
|
DockTab->SetContent(QuickFindWidgetSharedPtr.ToSharedRef());
|
|
QuickFindWidgetSharedPtr->SetParentTab(DockTab);
|
|
QuickFindWidgetWeakPtr = QuickFindWidgetSharedPtr;
|
|
QuickFindWidgetSharedPtr.Reset();
|
|
return DockTab;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::CloseQuickFindTab()
|
|
{
|
|
TSharedPtr<::Insights::SQuickFind> QuickFindWidget = QuickFindWidgetWeakPtr.Pin();
|
|
if (QuickFindWidget)
|
|
{
|
|
TSharedPtr<SDockTab> QuickFindTab = QuickFindWidget->GetParentTab().Pin();
|
|
if (QuickFindTab.IsValid())
|
|
{
|
|
QuickFindTab->RequestCloseTab();
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::HandleTrackVisibilityChanged()
|
|
{
|
|
if (HoveredTrack.IsValid())
|
|
{
|
|
HoveredTrack.Reset();
|
|
OnHoveredTrackChangedDelegate.Broadcast(HoveredTrack);
|
|
}
|
|
if (HoveredEvent.IsValid())
|
|
{
|
|
HoveredEvent.Reset();
|
|
OnHoveredEventChangedDelegate.Broadcast(HoveredEvent);
|
|
}
|
|
if (SelectedTrack.IsValid())
|
|
{
|
|
SelectedTrack.Reset();
|
|
OnSelectedTrackChangedDelegate.Broadcast(SelectedTrack);
|
|
}
|
|
if (SelectedEvent.IsValid())
|
|
{
|
|
SelectedEvent.Reset();
|
|
OnSelectedEventChangedDelegate.Broadcast(SelectedEvent);
|
|
}
|
|
Tooltip.SetDesiredOpacity(0.0f);
|
|
|
|
OnTrackVisibilityChangedDelegate.Broadcast();
|
|
FTimingProfilerManager::Get()->OnThreadFilterChanged();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::PreventThrottling()
|
|
{
|
|
bPreventThrottling = true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
TSharedPtr<FBaseTimingTrack> STimingView::FindTrack(uint64 InTrackId)
|
|
{
|
|
TSharedPtr<FBaseTimingTrack>* TrackPtrPtr = AllTracks.Find(InTrackId);
|
|
return TrackPtrPtr ? *TrackPtrPtr : nullptr;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::EnumerateTracks(TFunctionRef<void(TSharedPtr<FBaseTimingTrack> Track)> Callback)
|
|
{
|
|
for (auto& Entry : AllTracks)
|
|
{
|
|
Callback(Entry.Value);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
TArray<Timing::ITimingViewExtender*> STimingView::GetExtenders() const
|
|
{
|
|
return IModularFeatures::Get().GetModularFeatureImplementations<Timing::ITimingViewExtender>(Timing::TimingViewExtenderFeatureName);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#if UE_INSIGHTS_BACKWARD_COMPATIBILITY_UE54
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
TArray<::Insights::ITimingViewExtender*> STimingView::GetOldExtenders() const
|
|
{
|
|
return IModularFeatures::Get().GetModularFeatureImplementations<::Insights::ITimingViewExtender>(::Insights::TimingViewExtenderFeatureName);
|
|
}
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
#endif // UE_INSIGHTS_BACKWARD_COMPATIBILITY_UE54
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::ClearRelations()
|
|
{
|
|
CurrentRelations.Empty();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::FindFirstEvent()
|
|
{
|
|
if (SelectedEvent.IsValid())
|
|
{
|
|
SelectedEvent.Reset();
|
|
OnSelectedEventChangedDelegate.Broadcast(SelectedEvent);
|
|
}
|
|
FindNextEvent();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::FindPrevEvent()
|
|
{
|
|
TSharedPtr<const ITimingEvent> BestMatchEvent;
|
|
double StartTime = SelectedEvent.IsValid() ? SelectedEvent->GetStartTime() : std::numeric_limits<double>::max();
|
|
|
|
auto EventFilter = [StartTime](double EventStartTime, double EventEndTime, uint32 EventDepth)
|
|
{
|
|
return EventStartTime < StartTime;
|
|
};
|
|
FTimingEventSearchParameters Params(std::numeric_limits<double>::lowest(), StartTime, ETimingEventSearchFlags::StopAtFirstMatch, EventFilter);
|
|
Params.FilterExecutor = QuickFindVm->GetFilterConfigurator();
|
|
Params.SearchDirection = FTimingEventSearchParameters::ESearchDirection::Backward;
|
|
|
|
TSharedPtr<const FBaseTimingTrack> PriorityTrack = SelectedEvent.IsValid() ? SelectedEvent->GetTrack().ToSharedPtr() : nullptr;
|
|
EnumerateFilteredTracks(QuickFindVm->GetFilterConfigurator(), PriorityTrack, [&Params, &BestMatchEvent](TSharedPtr<const FBaseTimingTrack> Track)
|
|
{
|
|
if (!Track->IsVisible())
|
|
{
|
|
return;
|
|
}
|
|
|
|
Params.StartTime = BestMatchEvent.IsValid() ? BestMatchEvent->GetStartTime() : std::numeric_limits<double>::lowest();
|
|
|
|
TSharedPtr<const ITimingEvent> FoundEvent = Track->SearchEvent(Params);
|
|
if (FoundEvent.IsValid())
|
|
{
|
|
if (!BestMatchEvent.IsValid() || BestMatchEvent->GetStartTime() < FoundEvent->GetStartTime())
|
|
{
|
|
BestMatchEvent = FoundEvent;
|
|
}
|
|
}
|
|
});
|
|
|
|
if (BestMatchEvent)
|
|
{
|
|
SelectedEvent = BestMatchEvent;
|
|
BringIntoView(SelectedEvent->GetStartTime(), SelectedEvent->GetEndTime());
|
|
if (SelectedEvent->GetTrack()->GetLocation() == ETimingTrackLocation::Scrollable)
|
|
{
|
|
BringScrollableTrackIntoView(*SelectedEvent->GetTrack());
|
|
}
|
|
|
|
OnSelectedTimingEventChanged();
|
|
}
|
|
else
|
|
{
|
|
FMessageLog ReportMessageLog(FTimingProfilerManager::Get()->GetLogListingName());
|
|
ReportMessageLog.Error(LOCTEXT("NoEventFound", "No event found!"));
|
|
ReportMessageLog.Notify();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::FindNextEvent()
|
|
{
|
|
TSharedPtr<const ITimingEvent> BestMatchEvent;
|
|
double StartTime = SelectedEvent.IsValid() ? SelectedEvent->GetStartTime() : std::numeric_limits<double>::lowest();
|
|
|
|
auto EventFilter = [StartTime](double EventStartTime, double EventEndTime, uint32 EventDepth)
|
|
{
|
|
return EventStartTime > StartTime;
|
|
};
|
|
FTimingEventSearchParameters Params(StartTime, std::numeric_limits<double>::max(), ETimingEventSearchFlags::StopAtFirstMatch, EventFilter);
|
|
Params.FilterExecutor = QuickFindVm->GetFilterConfigurator();
|
|
|
|
TSharedPtr<const FBaseTimingTrack> PriorityTrack = SelectedEvent.IsValid() ? SelectedEvent->GetTrack().ToSharedPtr() : nullptr;
|
|
EnumerateFilteredTracks(QuickFindVm->GetFilterConfigurator(), PriorityTrack, [&Params, &BestMatchEvent](TSharedPtr<const FBaseTimingTrack> Track)
|
|
{
|
|
if (!Track->IsVisible())
|
|
{
|
|
return;
|
|
}
|
|
|
|
Params.EndTime = BestMatchEvent.IsValid() ? BestMatchEvent->GetStartTime() : std::numeric_limits<double>::max();
|
|
|
|
TSharedPtr<const ITimingEvent> FoundEvent = Track->SearchEvent(Params);
|
|
if (FoundEvent.IsValid())
|
|
{
|
|
if (BestMatchEvent.IsValid())
|
|
{
|
|
ensure(FoundEvent->GetStartTime() < BestMatchEvent->GetStartTime());
|
|
}
|
|
BestMatchEvent = FoundEvent;
|
|
}
|
|
});
|
|
|
|
if (BestMatchEvent)
|
|
{
|
|
SelectedEvent = BestMatchEvent;
|
|
BringIntoView(SelectedEvent->GetStartTime(), SelectedEvent->GetEndTime());
|
|
if (SelectedEvent->GetTrack()->GetLocation() == ETimingTrackLocation::Scrollable)
|
|
{
|
|
BringScrollableTrackIntoView(*SelectedEvent->GetTrack());
|
|
}
|
|
|
|
OnSelectedTimingEventChanged();
|
|
}
|
|
else
|
|
{
|
|
FMessageLog ReportMessageLog(FTimingProfilerManager::Get()->GetLogListingName());
|
|
ReportMessageLog.Error(LOCTEXT("NoEventFound", "No event found!"));
|
|
ReportMessageLog.Notify();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::FindLastEvent()
|
|
{
|
|
if (SelectedEvent.IsValid())
|
|
{
|
|
SelectedEvent.Reset();
|
|
OnSelectedEventChangedDelegate.Broadcast(SelectedEvent);
|
|
}
|
|
FindPrevEvent();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::FilterAllTracks()
|
|
{
|
|
LLM_SCOPE_BYTAG(Insights);
|
|
|
|
FilterConfigurator = MakeShared<UE::Insights::FFilterConfigurator>(*QuickFindVm->GetFilterConfigurator());
|
|
|
|
for (auto& Entry : AllTracks)
|
|
{
|
|
Entry.Value->SetFilterConfigurator(FilterConfigurator);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::ClearFilters()
|
|
{
|
|
LLM_SCOPE_BYTAG(Insights);
|
|
|
|
FilterConfigurator.Reset();
|
|
|
|
for (auto& Entry : AllTracks)
|
|
{
|
|
Entry.Value->SetFilterConfigurator(nullptr);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::PopulateTrackSuggestionList(const FString& Text, TArray<FString>& OutSuggestions)
|
|
{
|
|
for (auto& Entry : AllTracks)
|
|
{
|
|
if (Text.IsEmpty() || Entry.Value->GetName().Contains(Text))
|
|
{
|
|
OutSuggestions.Add(Entry.Value->GetName());
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::PopulateTimerNameSuggestionList(const FString& Text, TArray<FString>& OutSuggestions)
|
|
{
|
|
TSharedPtr<const TraceServices::IAnalysisSession> Session = FInsightsManager::Get()->GetSession();
|
|
if (Session.IsValid() && TraceServices::ReadTimingProfilerProvider(*Session.Get()))
|
|
{
|
|
TraceServices::FAnalysisSessionReadScope SessionReadScope(*Session.Get());
|
|
|
|
const TraceServices::ITimingProfilerProvider& TimingProfilerProvider = *TraceServices::ReadTimingProfilerProvider(*Session.Get());
|
|
|
|
const TraceServices::ITimingProfilerTimerReader* TimerReader;
|
|
TimingProfilerProvider.ReadTimers([&TimerReader](const TraceServices::ITimingProfilerTimerReader& Out) { TimerReader = &Out; });
|
|
|
|
uint32 TimerCount = TimerReader->GetTimerCount();
|
|
for (uint32 TimerIndex = 0; TimerIndex < TimerCount; ++TimerIndex)
|
|
{
|
|
const TraceServices::FTimingProfilerTimer* Timer = TimerReader->GetTimer(TimerIndex);
|
|
if (Timer && Timer->Name)
|
|
{
|
|
if (Text.IsEmpty())
|
|
{
|
|
OutSuggestions.Add(Timer->Name);
|
|
continue;
|
|
}
|
|
const TCHAR* FoundString = FCString::Stristr(Timer->Name, *Text);
|
|
if (FoundString)
|
|
{
|
|
OutSuggestions.Add(Timer->Name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::EnumerateFilteredTracks(TSharedPtr<UE::Insights::FFilterConfigurator> InFilterConfigurator, TSharedPtr<const FBaseTimingTrack> PriorityTrack, EnumerateFilteredTracksCallback Callback)
|
|
{
|
|
using EFilterField = UE::Insights::EFilterField;
|
|
UE::Insights::FFilterContext FilterContext;
|
|
FilterContext.AddFilterData(static_cast<int32>(EFilterField::TrackName), FString());
|
|
|
|
// Call the callback for the PriorityTrack first if it passes the filters.
|
|
// This is an optimization because in many cases, the next/prev event will be on the same track
|
|
// and searching this one first will potentially avoid searching all events on other tracks.
|
|
uint64 SkipId = std::numeric_limits<uint64>::max();
|
|
if (PriorityTrack.IsValid())
|
|
{
|
|
SkipId = PriorityTrack->GetId();
|
|
FilterContext.SetFilterData(static_cast<int32>(EFilterField::TrackName), PriorityTrack->GetName());
|
|
if (InFilterConfigurator->ApplyFilters(FilterContext))
|
|
{
|
|
Callback(PriorityTrack);
|
|
}
|
|
}
|
|
|
|
for (auto& Entry : AllTracks)
|
|
{
|
|
if (Entry.Value->GetId() == SkipId)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FilterContext.SetFilterData(static_cast<int32>(EFilterField::TrackName), Entry.Value->GetName());
|
|
if (InFilterConfigurator->ApplyFilters(FilterContext))
|
|
{
|
|
Callback(Entry.Value);
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
ETraceFrameType STimingView::GetFrameTypeToSnapTo()
|
|
{
|
|
TSharedPtr<STimersView> TimersView = GetTimersView();
|
|
if (TimersView.IsValid())
|
|
{
|
|
return TimersView->GetFrameTypeMode();
|
|
}
|
|
|
|
// TraceFrameType_Count is the Instance mode.
|
|
return ETraceFrameType::TraceFrameType_Count;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::SelectEventInstance(uint32 TimerId, ESelectEventType Type, bool bUseSelection)
|
|
{
|
|
SelectTimingEvent(nullptr, false, false);
|
|
|
|
double IntervalStart = 0.0f;
|
|
double IntervalEnd = std::numeric_limits<double>::infinity();
|
|
|
|
if (bUseSelection && SelectionEndTime > SelectionStartTime)
|
|
{
|
|
IntervalStart = SelectionStartTime;
|
|
IntervalEnd = SelectionEndTime;
|
|
}
|
|
|
|
TSharedPtr<const ITimingEvent> TimingEvent;
|
|
|
|
if (Type == ESelectEventType::Min)
|
|
{
|
|
TimingEvent = ThreadTimingSharedState->FindMinEventInstance(TimerId, IntervalStart, IntervalEnd);
|
|
}
|
|
else if (Type == ESelectEventType::Max)
|
|
{
|
|
TimingEvent = ThreadTimingSharedState->FindMaxEventInstance(TimerId, IntervalStart, IntervalEnd);
|
|
}
|
|
|
|
if (TimingEvent.IsValid())
|
|
{
|
|
SelectTimingEvent(TimingEvent, true, true);
|
|
}
|
|
else
|
|
{
|
|
FMessageLog ReportMessageLog(FTimingProfilerManager::Get()->GetLogListingName());
|
|
ReportMessageLog.Error(LOCTEXT("NoEventInstanceFound", "No event instance found!"));
|
|
ReportMessageLog.Notify();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void STimingView::UpdateFilters()
|
|
{
|
|
if (!bUpdateFilters)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (FInsightsManager::Get()->IsAnalysisComplete())
|
|
{
|
|
// This will be the final update.
|
|
bUpdateFilters = false;
|
|
}
|
|
|
|
if (FilterConfigurator.IsValid())
|
|
{
|
|
FilterConfigurator->Update();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool STimingView::IsInTimingProfiler() const
|
|
{
|
|
return GetName() == FInsightsManagerTabs::TimingProfilerTabId;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
TSharedPtr<STimersView> STimingView::GetTimersView() const
|
|
{
|
|
if (IsInTimingProfiler())
|
|
{
|
|
TSharedPtr<STimingProfilerWindow> TimingWindow = FTimingProfilerManager::Get()->GetProfilerWindow();
|
|
if (TimingWindow.IsValid())
|
|
{
|
|
return TimingWindow->GetTimersView();
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
TSharedPtr<SLogView> STimingView::GetLogView() const
|
|
{
|
|
if (IsInTimingProfiler())
|
|
{
|
|
TSharedPtr<STimingProfilerWindow> TimingWindow = FTimingProfilerManager::Get()->GetProfilerWindow();
|
|
if (TimingWindow.IsValid())
|
|
{
|
|
return TimingWindow->GetLogView();
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
} // namespace UE::Insights::TimingProfiler
|
|
|
|
#undef INSIGHTS_ACTIVATE_BENCHMARK
|
|
#undef LOCTEXT_NAMESPACE
|