// Copyright Epic Games, Inc. All Rights Reserved. #include "SVisualLoggerTimelinesContainer.h" #include "SVisualLoggerTimeline.h" #include "Framework/Application/SlateApplication.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "VisualLoggerDatabase.h" #include "LogVisualizerStyle.h" #include "VisualLoggerTimeSliderController.h" #include "SVisualLoggerReport.h" #define LOCTEXT_NAMESPACE "STimelinesContainer" TSharedRef SVisualLoggerTimelinesContainer::GetRightClickMenuContent() { FMenuBuilder MenuBuilder(true, NULL); MenuBuilder.BeginSection("VisualLogReports", LOCTEXT("VisualLogReports", "VisualLog Reports")); { MenuBuilder.AddMenuEntry( LOCTEXT("GenerateReport", "Generate Report"), LOCTEXT("GenerateReportTooltip", "Generate report from Visual Log events."), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &SVisualLoggerTimelinesContainer::GenerateReport)) ); } MenuBuilder.EndSection(); FDisplayMetrics DisplayMetrics; FSlateApplication::Get().GetCachedDisplayMetrics(DisplayMetrics); const FVector2D DisplaySize( DisplayMetrics.PrimaryDisplayWorkAreaRect.Right - DisplayMetrics.PrimaryDisplayWorkAreaRect.Left, DisplayMetrics.PrimaryDisplayWorkAreaRect.Bottom - DisplayMetrics.PrimaryDisplayWorkAreaRect.Top); return SNew(SVerticalBox) + SVerticalBox::Slot() .MaxHeight(DisplaySize.Y * 0.9) [ MenuBuilder.MakeWidget() ]; } void SVisualLoggerTimelinesContainer::SetSelectionState(TSharedPtr AffectedNode, bool bSelect, bool bDeselectOtherNodes) { const FName RowName = AffectedNode->GetName(); const bool bIsSelected = FVisualLoggerDatabase::Get().IsRowSelected(RowName); if (bSelect && (!bIsSelected || bDeselectOtherNodes)) { FVisualLoggerDatabase::Get().SelectRow(RowName, bDeselectOtherNodes); } else if (!bSelect && bIsSelected && AffectedNode.IsValid()) { FVisualLoggerDatabase::Get().DeselectRow(RowName); } } void SVisualLoggerTimelinesContainer::OnObjectSelectionChanged(const TArray& RowNames) { CachedSelectedTimelines.Reset(); for (TSharedPtr& Timeline : TimelineItems) { if (RowNames.Find(Timeline->GetName()) != INDEX_NONE) { CachedSelectedTimelines.Add(Timeline); } } if (CachedSelectedTimelines.Num() >= 1) { FSlateApplication::Get().SetKeyboardFocus(SharedThis(CachedSelectedTimelines[CachedSelectedTimelines.Num()-1].Get()), EFocusCause::Navigation); } } bool SVisualLoggerTimelinesContainer::IsNodeSelected(TSharedPtr Node) const { return FVisualLoggerDatabase::Get().IsRowSelected(Node->GetName());// SelectedNodes.Contains(Node); } void SVisualLoggerTimelinesContainer::ChangeSelection(TSharedPtr InTimeline, const FPointerEvent& MouseEvent) { if (MouseEvent.IsLeftShiftDown() == false) { if (MouseEvent.IsLeftControlDown()) { SetSelectionState(InTimeline, !InTimeline->IsSelected(), false); } else { SetSelectionState(InTimeline, true, true); } } else { if (CachedSelectedTimelines.Num() == 0 && TimelineItems.Num()) { SetSelectionState(TimelineItems[0], true, true); } TSharedPtr LastSelected = CachedSelectedTimelines.Num() ? CachedSelectedTimelines[CachedSelectedTimelines.Num() - 1] : nullptr; if (LastSelected.IsValid()) { bool bStartedSelection = false; for (TSharedPtr& TimelineItem : TimelineItems) { if (TimelineItem == LastSelected || InTimeline == TimelineItem) { if (!bStartedSelection) { bStartedSelection = true; } else { bStartedSelection = false; break; } } if (bStartedSelection) { SetSelectionState(TimelineItem, true, false); } } } SetSelectionState(InTimeline, true, false); } } FReply SVisualLoggerTimelinesContainer::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { if (MouseEvent.GetEffectingButton() != EKeys::LeftMouseButton) { return TimeSliderController->OnMouseButtonDown(*this, MyGeometry, MouseEvent); } return FReply::Unhandled(); } FReply SVisualLoggerTimelinesContainer::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { if (MouseEvent.GetEffectingButton() != EKeys::LeftMouseButton) { return TimeSliderController->OnMouseButtonUp(*this, MyGeometry, MouseEvent); } return FReply::Unhandled(); } FReply SVisualLoggerTimelinesContainer::OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { if (MouseEvent.GetEffectingButton() != EKeys::LeftMouseButton) { return TimeSliderController->OnMouseMove(*this, MyGeometry, MouseEvent); } return FReply::Unhandled(); } FReply SVisualLoggerTimelinesContainer::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) { if (InKeyEvent.GetKey() == EKeys::A && InKeyEvent.IsLeftControlDown()) { for (TSharedPtr& Timeline : TimelineItems) { SetSelectionState(Timeline, true, false); } return FReply::Handled(); } else if (InKeyEvent.GetKey() == EKeys::Platform_Delete && CachedSelectedTimelines.Num() > 0) { TWeakPtr NotSelectedOne; for (TSharedPtr& CurrentNode : CachedSelectedTimelines) { TSharedPtr LastSelected = CachedSelectedTimelines[CachedSelectedTimelines.Num() - 1]; bool bFoundSelectedOne = false; for (TSharedPtr& Timeline : TimelineItems) { if (IsNodeSelected(Timeline) == false) { NotSelectedOne = Timeline; } if (LastSelected == Timeline) { if (bFoundSelectedOne && NotSelectedOne.IsValid()) { break; } bFoundSelectedOne = true; } } FVisualLoggerDatabase::Get().RemoveRow(CurrentNode->GetName()); TimelineItems.Remove(CurrentNode); ContainingBorder->RemoveSlot(CurrentNode.ToSharedRef()); } if (NotSelectedOne.IsValid()) { SetSelectionState(NotSelectedOne.Pin(), true, true); } return FReply::Handled(); } else if (InKeyEvent.GetKey() == EKeys::Up || InKeyEvent.GetKey() == EKeys::Down) { TSharedPtr PreviousTimeline; TSharedPtr LastSelected = CachedSelectedTimelines[CachedSelectedTimelines.Num() - 1]; for (int32 Index = 0; Index < TimelineItems.Num(); ++Index) { auto& CurrentItem = TimelineItems[Index]; if (LastSelected == CurrentItem) { if (InKeyEvent.GetKey() == EKeys::Up && PreviousTimeline.IsValid()) { SetSelectionState(PreviousTimeline, true, true); } else if (InKeyEvent.GetKey() == EKeys::Down) { // let's find next visible time line if (TimelineItems.IsValidIndex(Index + 1)) { for (int32 i = Index + 1; i < TimelineItems.Num(); ++i) { if (TimelineItems[i]->GetVisibility() == EVisibility::Visible) { SetSelectionState(TimelineItems[i], true, true); break; } } } } break; } if (CurrentItem->GetVisibility() == EVisibility::Visible) { PreviousTimeline = CurrentItem; } } return FReply::Handled(); } return FReply::Unhandled(); } void SVisualLoggerTimelinesContainer::ResetData() { for (TSharedPtr& Timeline : TimelineItems) { ContainingBorder->RemoveSlot(Timeline.ToSharedRef()); Timeline.Reset(); } TimelineItems.Reset(); for (auto& CurrentItem : CachedSelectedTimelines) { CurrentItem.Reset(); } CachedSelectedTimelines.Reset(); CachedMinTime = MAX_dbl; CachedMaxTime = 0.; TimeSliderController->SetClampRange(0, 5); TimeSliderController->SetTimeRange(0, 5); } void SVisualLoggerTimelinesContainer::Construct(const FArguments& InArgs, TSharedRef InVisualLoggerView, TSharedRef InTimeSliderController) { TimeSliderController = InTimeSliderController; VisualLoggerView = InVisualLoggerView; ChildSlot [ SNew(SBorder) .Padding(0) .VAlign(VAlign_Top) .BorderImage(FLogVisualizerStyle::Get().GetBrush("ToolPanel.GroupBorder")) [ SAssignNew(ContainingBorder, SVerticalBox) ] ]; CachedMinTime = MAX_dbl; CachedMaxTime = 0.; FVisualLoggerDatabase::Get().GetEvents().OnNewRow.AddRaw(this, &SVisualLoggerTimelinesContainer::OnNewRowHandler); FVisualLoggerDatabase::Get().GetEvents().OnNewItem.AddRaw(this, &SVisualLoggerTimelinesContainer::OnNewItemHandler); FVisualLoggerDatabase::Get().GetEvents().OnRowSelectionChanged.AddRaw(this, &SVisualLoggerTimelinesContainer::OnObjectSelectionChanged); FVisualLoggerDatabase::Get().GetEvents().OnRowChangedVisibility.AddRaw(this, &SVisualLoggerTimelinesContainer::OnRowChangedVisibility); } SVisualLoggerTimelinesContainer::~SVisualLoggerTimelinesContainer() { FVisualLoggerDatabase::Get().GetEvents().OnNewRow.RemoveAll(this); FVisualLoggerDatabase::Get().GetEvents().OnNewItem.RemoveAll(this); FVisualLoggerDatabase::Get().GetEvents().OnRowSelectionChanged.RemoveAll(this); FVisualLoggerDatabase::Get().GetEvents().OnRowChangedVisibility.RemoveAll(this); } void SVisualLoggerTimelinesContainer::OnNewRowHandler(const FVisualLoggerDBRow& DBRow) { TSharedPtr NewTimeline; ContainingBorder->AddSlot() [ SAssignNew(NewTimeline, SLogVisualizerTimeline, TimeSliderController, SharedThis(this), DBRow.GetOwnerName(), DBRow.GetOwnerDisplayName(), DBRow.GetOwnerClassName()) .OnGetMenuContent(this, &SVisualLoggerTimelinesContainer::GetRightClickMenuContent) ]; TimelineItems.Add(NewTimeline.ToSharedRef()); // make sure the new entry doesn't show if it doesn't match CurrentSearchText NewTimeline->OnSearchChanged(CurrentSearchText); } void SVisualLoggerTimelinesContainer::OnNewItemHandler(const FVisualLoggerDBRow& BDRow, int32 ItemIndex) { const FVisualLogDevice::FVisualLogEntryItem& Entry = BDRow.GetItems()[ItemIndex]; CachedMinTime = CachedMinTime < Entry.Entry.TimeStamp ? CachedMinTime : Entry.Entry.TimeStamp; CachedMaxTime = CachedMaxTime > Entry.Entry.TimeStamp ? CachedMaxTime : Entry.Entry.TimeStamp; TRange LocalViewRange = TimeSliderController->GetTimeSliderArgs().ViewRange.Get(); TRange ClampRange = TimeSliderController->GetTimeSliderArgs().ClampRange.Get(); double ZoomLevel = LocalViewRange.Size() / ClampRange.Size(); TimeSliderController->GetTimeSliderArgs().ClampRange = TRange(CachedMinTime, CachedMaxTime + 0.1); TimeSliderController->GetTimeSliderArgs().ViewRange = TimeSliderController->GetTimeSliderArgs().ClampRange; } void SVisualLoggerTimelinesContainer::OnSearchChanged(const FText& Filter) { CurrentSearchText = Filter; for (TSharedPtr& Timeline : TimelineItems) { Timeline->OnSearchChanged(Filter); } } void SVisualLoggerTimelinesContainer::OnFiltersChanged() { for (TSharedPtr& Timeline : TimelineItems) { Timeline->OnFiltersChanged(); } } void SVisualLoggerTimelinesContainer::OnFiltersSearchChanged(const FText& Filter) { for (TSharedPtr& Timeline : TimelineItems) { Timeline->OnFiltersSearchChanged(Filter); } } void SVisualLoggerTimelinesContainer::GenerateReport() { TSharedRef NewWindow = SNew(SWindow) .ClientSize(FVector2f(720.f, 768.f)) .Title(NSLOCTEXT("LogVisualizerReport", "WindowTitle", "Log Visualizer Report")) [ SNew(SVisualLoggerReport, CachedSelectedTimelines, VisualLoggerView) ]; FSlateApplication::Get().AddWindow(NewWindow); } void SVisualLoggerTimelinesContainer::OnRowChangedVisibility(const FName& InName) { for (TSharedPtr& Timeline : TimelineItems) { if (InName == Timeline->GetName()) { Timeline->SetVisibility(FVisualLoggerDatabase::Get().IsRowVisible(InName) ? EVisibility::Visible : EVisibility::Collapsed); break; } } } #undef LOCTEXT_NAMESPACE