// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved. #include "LogVisualizerPCH.h" #include "SLogBar.h" const float SLogBar::SubPixelMinSize = 3.0f; const float SLogBar::TimeUnit = 1.f/60.f; const float SLogBar::MaxUnitSizePx = 16.f; void SLogBar::Construct( const FArguments& InArgs ) { OnSelectionChanged = InArgs._OnSelectionChanged; OnGeometryChanged = InArgs._OnGeometryChanged; ShouldDrawSelection = InArgs._ShouldDrawSelection; DisplayedTime = InArgs._DisplayedTime; CurrentEntryIndex = InArgs._CurrentEntryIndex; BackgroundImage = FEditorStyle::GetBrush("LogVisualizer.LogBar.Background"); FillImage = FEditorStyle::GetBrush("LogVisualizer.LogBar.EntryDefault"); SelectedImage = FEditorStyle::GetBrush("LogVisualizer.LogBar.Selected"); TimeMarkImage = FEditorStyle::GetBrush("LogVisualizer.LogBar.TimeMark"); LastHoveredEvent = INDEX_NONE; Zoom = 1.0f; Offset = 0.0f; StartTime = 0.0; TotalTime = 1.0; RowIndex = INDEX_NONE; bShouldDrawSelection = false; } int32 SLogBar::OnPaint( const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const { // Used to track the layer ID we will return. int32 RetLayerId = LayerId; bool bEnabled = ShouldBeEnabled( bParentEnabled ); const ESlateDrawEffect::Type DrawEffects = bEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect; const FColor ColorAndOpacitySRGB = InWidgetStyle.GetColorAndOpacityTint(); static const FColor SelectedBarColor(255, 255, 255, 255); static const FColor CurrentTimeColor(140, 255, 255, 255); // Paint inside the border only. const FVector2D BorderPadding = FEditorStyle::GetVector("ProgressBar.BorderPadding"); FPaintGeometry ForegroundPaintGeometry = AllottedGeometry.ToInflatedPaintGeometry( -BorderPadding ); const FSlateRect ForegroundClippingRect = ForegroundPaintGeometry.ToSlateRect().IntersectionWith( MyClippingRect ); const float LogBarWidth = AllottedGeometry.Size.X; FSlateDrawElement::MakeBox( OutDrawElements, RetLayerId++, AllottedGeometry.ToPaintGeometry(), BackgroundImage, MyClippingRect, DrawEffects, ColorAndOpacitySRGB ); // Draw all bars int32 EntryIndex = 0; while (EntryIndex < Entries.Num()) { float CurrentStartX, CurrentEndX; { TSharedPtr Entry = Entries[EntryIndex]; if (!Entry.IsValid() || !CalculateEntryGeometry(Entry.Get(), AllottedGeometry, CurrentStartX, CurrentEndX)) { EntryIndex++; continue; } } // find bar width, connect all contiguous bars to draw them as one geometry (rendering optimization) float BarWidth = 0; int32 StartIndex = EntryIndex; float LastEndX = MAX_FLT; for (; StartIndex < Entries.Num(); ++StartIndex) { TSharedPtr Entry = Entries[StartIndex]; float StartX, EndX; if (!Entry.IsValid() || !CalculateEntryGeometry(Entry.Get(), AllottedGeometry, StartX, EndX)) { break; } if (StartX > LastEndX) { break; } BarWidth = EndX - CurrentStartX; LastEndX = EndX; } if (BarWidth > 0) { // Draw Event bar FSlateDrawElement::MakeBox( OutDrawElements, RetLayerId++, AllottedGeometry.ToPaintGeometry( FVector2D(CurrentStartX, 0.0f), FVector2D(BarWidth, AllottedGeometry.Size.Y)), FillImage, ForegroundClippingRect, DrawEffects, SLogVisualizer::GetColorForUsedCategory(0) ); } EntryIndex = StartIndex; } // draw "current time" { const float RelativePos = Offset + (DisplayedTime.Execute() - StartTime) / TotalTime * Zoom; const float PosX = (float)(LogBarWidth * RelativePos); const float ClampedSize = FMath::Clamp(TimeUnit / TotalTime * Zoom, 0.0f, 1.0f ); float CurrentTimeMarkWidth = (float)FMath::Max(LogBarWidth * ClampedSize , ClampedSize > 0.0f ? SubPixelMinSize : 0.0f); // Draw Event bar FSlateDrawElement::MakeBox( OutDrawElements, RetLayerId++, AllottedGeometry.ToPaintGeometry( FVector2D( PosX - 2.f, 0.0f ), FVector2D( CurrentTimeMarkWidth + 4.f, AllottedGeometry.Size.Y )), TimeMarkImage, ForegroundClippingRect, DrawEffects, CurrentTimeColor ); } if (bShouldDrawSelection) { EntryIndex = CurrentEntryIndex.IsBound() ? CurrentEntryIndex.Execute() : INDEX_NONE; if (Entries.IsValidIndex(EntryIndex) == true) { TSharedPtr Entry = Entries[EntryIndex]; float StartX, EndX; if (CalculateEntryGeometry( Entry.Get(), AllottedGeometry, StartX, EndX ) ) { // Draw Event bar FSlateDrawElement::MakeBox( OutDrawElements, RetLayerId++, AllottedGeometry.ToPaintGeometry( FVector2D( StartX, 0.0f ), FVector2D( EndX - StartX, AllottedGeometry.Size.Y )), SelectedImage, ForegroundClippingRect, DrawEffects, SelectedBarColor ); } } } return RetLayerId - 1; } void SLogBar::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime ) { if( OnGeometryChanged.IsBound() ) { if( AllottedGeometry != this->LastGeometry ) { OnGeometryChanged.ExecuteIfBound(AllottedGeometry); LastGeometry = AllottedGeometry; } } bShouldDrawSelection = ShouldDrawSelection.IsBound() && ShouldDrawSelection.Execute() == true; SLeafWidget::Tick( AllottedGeometry, InCurrentTime, InDeltaTime ); } void SLogBar::SelectEntry(const FGeometry& MyGeometry, const float ClickX) { int32 BestDistance = 0x7fffffff; int32 LastDistance = 0x7fffffff; // Go through all events and check if at least one has been clicked int32 BestIndex = INDEX_NONE; int32 EntryIndex = INDEX_NONE; if (Entries.Num() == 1) { BestIndex = 0; } else { for (EntryIndex = 0; EntryIndex < Entries.Num(); ++EntryIndex) { TSharedPtr Entry = Entries[EntryIndex]; float StartX, EndX; if (CalculateEntryGeometry(Entry.Get(), MyGeometry, StartX, EndX)) { const int32 Distance = FMath::Abs(ClickX - StartX); if (Distance < BestDistance) { BestDistance = Distance; BestIndex = EntryIndex; } else if (Distance > LastDistance) { break; } LastDistance = Distance; } } } SelectEntryAtIndex(BestIndex); } int32 SLogBar::GetEntryIndexAtTime(float Time) const { int BestIndex = INDEX_NONE; float BestTimeDiff = MAX_FLT; float LastTimeDiff = MAX_FLT; // Go through all events and check if at least one has been clicked int32 EntryIndex = INDEX_NONE; for( EntryIndex = 0; EntryIndex < Entries.Num(); ++EntryIndex ) { TSharedPtr Entry = Entries[EntryIndex]; const float TimeDiff = FMath::Abs(Entry->TimeStamp - Time); if (TimeDiff < BestTimeDiff) { BestTimeDiff = TimeDiff; BestIndex = EntryIndex; } else if (TimeDiff > LastTimeDiff) { break; } LastTimeDiff = TimeDiff; } return BestIndex; } void SLogBar::SelectEntryAtIndex(const int32 Index) { if (Entries.IsValidIndex(Index) == false) { return; } // Execute OnSelectionChanged delegate if( OnSelectionChanged.IsBound() ) { OnSelectionChanged.ExecuteIfBound(Entries[Index]); } } FReply SLogBar::OnMouseButtonDown( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) { if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton) { // Translate click position from absolute to graph space const float ClickX = MyGeometry.AbsoluteToLocal( MouseEvent.GetScreenSpacePosition() ).X;//( MyGeometry.AbsoluteToLocal( MouseEvent.GetScreenSpacePosition() ).X / MyGeometry.Size.X ) / Zoom - Offset / Zoom; SelectEntry(MyGeometry, ClickX); return FReply::Handled(); } else { return FReply::Unhandled(); } } FReply SLogBar::OnMouseButtonUp( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) { // same behavior as when OnMouseButtonDown return OnMouseButtonDown(MyGeometry, MouseEvent); } /** * The system calls this method to notify the widget that a mouse moved within it. This event is bubbled. * * @param MyGeometry The Geometry of the widget receiving the event * @param MouseEvent Information about the input event * * @return Whether the event was handled along with possible requests for the system to take action. */ FReply SLogBar::OnMouseMove( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) { const float HoverX = ( MyGeometry.AbsoluteToLocal( MouseEvent.GetScreenSpacePosition() ).X / MyGeometry.Size.X ) / Zoom - Offset / Zoom; int32 HoveredEventIndex = INDEX_NONE; for( int32 EntryIndex = 0; EntryIndex < Entries.Num(); EntryIndex++ ) { TSharedPtr Entry = Entries[EntryIndex]; if( HoverX >= Entry->TimeStamp && Entry->TimeStamp <= HoverX + 1) { HoveredEventIndex = EntryIndex; break; } } if( HoveredEventIndex != LastHoveredEvent ) { /*if( HoveredEventIndex != INDEX_NONE ) { this->SetToolTipText( Entries[ HoveredEventIndex ]->EventName ); } else { this->SetToolTipText( TEXT( "" ) ); }*/ LastHoveredEvent = HoveredEventIndex; } if (MouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton)) { SelectEntry(MyGeometry, MyGeometry.AbsoluteToLocal( MouseEvent.GetScreenSpacePosition() ).X); } return SLeafWidget::OnMouseMove( MyGeometry, MouseEvent ); } FVector2D SLogBar::ComputeDesiredSize() const { return FVector2D( 500.0f, 24.0f ); } void SLogBar::SetEntries(const TArray >& InEntries, float InStartTime, float InTotalTime) { Entries = InEntries; StartTime = InStartTime; TotalTime = InTotalTime; } void SLogBar::OnCurrentTimeChanged(float NewTime) { const bool bUpdateSelection = ShouldDrawSelection.IsBound() && ShouldDrawSelection.Execute() == true ; if (bUpdateSelection) { // find entry closest to desired time FSimpleDelegateGraphTask::CreateAndDispatchWhenReady( FSimpleDelegateGraphTask::FDelegate::CreateSP(this, &SLogBar::SelectEntryAtIndex, GetEntryIndexAtTime(NewTime)) , TEXT("Updating Log Entry shown in LogVisualizer") , NULL , ENamedThreads::GameThread ); } } void SLogBar::SetZoomAndOffset(float InZoom, float InOffset) { SetZoom(InZoom); SetOffset(InOffset); } void SLogBar::UpdateShouldDrawSelection() { /*const bool bNewShouldDraw = ShouldDrawSelection.Execute(); if (bNewShouldDraw != !!bShouldDrawSelection) { bShouldDrawSelection = bNewShouldDraw; if (bNewShouldDraw) { check(DisplayedTime.IsBound()); SelectEntryAtIndex(GetEntryIndexAtTime(DisplayedTime.Execute())); } }*/ }