FSequencerSnapField::FSequencerSnapField(const ISequencer& InSequencer, ISequencerSnapCandidate& Candidate, uint32 EntityMask) { TSharedPtr<SSequencerTreeView> TreeView = StaticCastSharedRef<SSequencer>(InSequencer.GetSequencerWidget())->GetTreeView(); TArray<TSharedRef<FSequencerDisplayNode>> VisibleNodes; for (const SSequencerTreeView::FCachedGeometry& Geometry : TreeView->GetAllVisibleNodes()) { VisibleNodes.Add(Geometry.Node); } auto ViewRange = InSequencer.GetViewRange(); FSequencerEntityWalker Walker(ViewRange); // Traverse the visible space, collecting snapping times as we go FSnapGridVisitor Visitor(Candidate, EntityMask); Walker.Traverse(Visitor, VisibleNodes); // Add the playback range start/end bounds as potential snap candidates TRange<float> PlaybackRange = InSequencer.GetFocusedMovieSceneSequence()->GetMovieScene()->GetPlaybackRange(); Visitor.Snaps.Add(FSequencerSnapPoint{ FSequencerSnapPoint::PlaybackRange, PlaybackRange.GetLowerBoundValue() }); Visitor.Snaps.Add(FSequencerSnapPoint{ FSequencerSnapPoint::PlaybackRange, PlaybackRange.GetUpperBoundValue() }); // Add the current time as a potential snap candidate Visitor.Snaps.Add(FSequencerSnapPoint{ FSequencerSnapPoint::CurrentTime, InSequencer.GetGlobalTime() }); // Add the selection range bounds as a potential snap candidate TRange<float> SelectionRange = InSequencer.GetFocusedMovieSceneSequence()->GetMovieScene()->GetSelectionRange(); Visitor.Snaps.Add(FSequencerSnapPoint{ FSequencerSnapPoint::InOutRange, SelectionRange.GetLowerBoundValue() }); Visitor.Snaps.Add(FSequencerSnapPoint{ FSequencerSnapPoint::InOutRange, SelectionRange.GetUpperBoundValue() }); // Sort Visitor.Snaps.Sort([](const FSequencerSnapPoint& A, const FSequencerSnapPoint& B){ return A.Time < B.Time; }); // Remove duplicates for (int32 Index = 0; Index < Visitor.Snaps.Num(); ++Index) { const float CurrentTime = Visitor.Snaps[Index].Time; int32 NumToMerge = 0; for (int32 DuplIndex = Index + 1; DuplIndex < Visitor.Snaps.Num(); ++DuplIndex) { if (!FMath::IsNearlyEqual(CurrentTime, Visitor.Snaps[DuplIndex].Time)) { break; } ++NumToMerge; } if (NumToMerge) { Visitor.Snaps.RemoveAt(Index + 1, NumToMerge, false); } } SortedSnaps = MoveTemp(Visitor.Snaps); }
void FSequencerTimeSliderController::SetPlaybackRangeStart(float NewStart) { TRange<float> PlaybackRange = TimeSliderArgs.PlaybackRange.Get(); if (NewStart <= PlaybackRange.GetUpperBoundValue()) { TimeSliderArgs.OnPlaybackRangeChanged.ExecuteIfBound(TRange<float>(NewStart, PlaybackRange.GetUpperBoundValue())); } }
void FSequencer::Tick(float InDeltaTime) { if (bNeedTreeRefresh) { // @todo - Sequencer Will be called too often UpdateRuntimeInstances(); SequencerWidget->UpdateLayoutTree(); bNeedTreeRefresh = false; } float NewTime = GetGlobalTime() + InDeltaTime; if (PlaybackState == EMovieScenePlayerStatus::Playing || PlaybackState == EMovieScenePlayerStatus::Recording) { TRange<float> TimeBounds = GetTimeBounds(); if (!TimeBounds.IsEmpty()) { if (NewTime > TimeBounds.GetUpperBoundValue()) { if (bLoopingEnabled) { NewTime -= TimeBounds.Size<float>(); } else { NewTime = TimeBounds.GetUpperBoundValue(); PlaybackState = EMovieScenePlayerStatus::Stopped; } } if (NewTime < TimeBounds.GetLowerBoundValue()) { NewTime = TimeBounds.GetLowerBoundValue(); } SetGlobalTime(NewTime); } else { // no bounds at all, stop playing PlaybackState = EMovieScenePlayerStatus::Stopped; } } // Tick all the tools we own as well for (int32 EditorIndex = 0; EditorIndex < TrackEditors.Num(); ++EditorIndex) { TrackEditors[EditorIndex]->Tick(InDeltaTime); } }
void FVisualLoggerTimeSliderController::HorizontalScrollBar_OnUserScrolled(float ScrollOffset) { if (!TimeSliderArgs.ViewRange.IsBound()) { TRange<float> LocalViewRange = TimeSliderArgs.ViewRange.Get(); float LocalViewRangeMin = LocalViewRange.GetLowerBoundValue(); float LocalViewRangeMax = LocalViewRange.GetUpperBoundValue(); float LocalClampMin = TimeSliderArgs.ClampRange.Get().GetLowerBoundValue(); float LocalClampMax = TimeSliderArgs.ClampRange.Get().GetUpperBoundValue(); float InThumbSizeFraction = (LocalViewRangeMax - LocalViewRangeMin) / (LocalClampMax - LocalClampMin); float NewViewOutputMin = LocalClampMin + ScrollOffset * (LocalClampMax - LocalClampMin); // The output is not bound to a delegate so we'll manage the value ourselves float NewViewOutputMax = FMath::Min<float>(NewViewOutputMin + (LocalViewRangeMax - LocalViewRangeMin), LocalClampMax); NewViewOutputMin = NewViewOutputMax - (LocalViewRangeMax - LocalViewRangeMin); float InOffsetFraction = (NewViewOutputMin - LocalClampMin) / (LocalClampMax - LocalClampMin); //if (InOffsetFraction + InThumbSizeFraction <= 1) { TimeSliderArgs.ViewRange.Set(TRange<float>(NewViewOutputMin, NewViewOutputMax)); Scrollbar->SetState(InOffsetFraction, InThumbSizeFraction); } } }
int32 FSequencerTimeSliderController::OnPaintTimeSlider( bool bMirrorLabels, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const { const bool bEnabled = bParentEnabled; const ESlateDrawEffect::Type DrawEffects = bEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect; TRange<float> LocalViewRange = TimeSliderArgs.ViewRange.Get(); const float LocalViewRangeMin = LocalViewRange.GetLowerBoundValue(); const float LocalViewRangeMax = LocalViewRange.GetUpperBoundValue(); const float LocalSequenceLength = LocalViewRangeMax-LocalViewRangeMin; FVector2D Scale = FVector2D(1.0f,1.0f); if ( LocalSequenceLength > 0) { FScrubRangeToScreen RangeToScreen( LocalViewRange, AllottedGeometry.Size ); const float MajorTickHeight = 9.0f; FDrawTickArgs Args; Args.AllottedGeometry = AllottedGeometry; Args.bMirrorLabels = bMirrorLabels; Args.bOnlyDrawMajorTicks = false; Args.TickColor = FLinearColor::White; Args.ClippingRect = MyClippingRect; Args.DrawEffects = DrawEffects; Args.StartLayer = LayerId; Args.TickOffset = bMirrorLabels ? 0.0f : FMath::Abs( AllottedGeometry.Size.Y - MajorTickHeight ); Args.MajorTickHeight = MajorTickHeight; DrawTicks( OutDrawElements, RangeToScreen, Args ); const float HandleSize = 13.0f; float HalfSize = FMath::TruncToFloat(HandleSize/2.0f); // Draw the scrub handle const float XPos = RangeToScreen.InputToLocalX( TimeSliderArgs.ScrubPosition.Get() ); // Should draw above the text const int32 ArrowLayer = LayerId + 2; FPaintGeometry MyGeometry = AllottedGeometry.ToPaintGeometry( FVector2D( XPos-HalfSize, 0 ), FVector2D( HandleSize, AllottedGeometry.Size.Y ) ); FLinearColor ScrubColor = InWidgetStyle.GetColorAndOpacityTint(); // @todo Sequencer this color should be specified in the style ScrubColor.A = ScrubColor.A*0.5f; ScrubColor.B *= 0.1f; ScrubColor.G *= 0.2f; FSlateDrawElement::MakeBox( OutDrawElements, ArrowLayer, MyGeometry, bMirrorLabels ? ScrubHandleUp : ScrubHandleDown, MyClippingRect, DrawEffects, ScrubColor ); return ArrowLayer; } return LayerId; }
FReply FSequencer::OnPlay() { if( PlaybackState == EMovieScenePlayerStatus::Playing || PlaybackState == EMovieScenePlayerStatus::Recording ) { PlaybackState = EMovieScenePlayerStatus::Stopped; // Update on stop (cleans up things like sounds that are playing) RootMovieSceneInstance->Update( ScrubPosition, ScrubPosition, *this ); } else { TRange<float> TimeBounds = GetTimeBounds(); if (!TimeBounds.IsEmpty()) { float CurrentTime = GetGlobalTime(); if (CurrentTime < TimeBounds.GetLowerBoundValue() || CurrentTime >= TimeBounds.GetUpperBoundValue()) { SetGlobalTime(TimeBounds.GetLowerBoundValue()); } PlaybackState = EMovieScenePlayerStatus::Playing; // Make sure Slate ticks during playback SequencerWidget->RegisterActiveTimerForPlayback(); } } return FReply::Handled(); }
void FVisualLoggerTimeSliderController::SetClampRange(float MinValue, float MaxValue) { TRange<float> LocalViewRange = TimeSliderArgs.ViewRange.Get(); float LocalClampMin = TimeSliderArgs.ClampRange.Get().GetLowerBoundValue(); float LocalClampMax = TimeSliderArgs.ClampRange.Get().GetUpperBoundValue(); const float CurrentDistance = LocalClampMax - LocalClampMin; const float ZoomDelta = (LocalViewRange.GetUpperBoundValue() - LocalViewRange.GetLowerBoundValue()) / CurrentDistance; MaxValue = MinValue + (MaxValue - MinValue < 2 ? CurrentDistance : MaxValue - MinValue); TimeSliderArgs.ClampRange = TRange<float>(MinValue, MaxValue); const float LocalViewRangeMin = FMath::Clamp(LocalViewRange.GetLowerBoundValue(), MinValue, MaxValue); const float LocalViewRangeMax = FMath::Clamp(LocalViewRange.GetUpperBoundValue(), MinValue, MaxValue); SetTimeRange(ZoomDelta >= 1 ? MinValue : LocalViewRangeMin, ZoomDelta >= 1 ? MaxValue : LocalViewRangeMax); }
FReply FSequencer::OnStepToEnd() { PlaybackState = EMovieScenePlayerStatus::Stopped; TRange<float> TimeBounds = GetTimeBounds(); if (!TimeBounds.IsEmpty()) { SetGlobalTime(TimeBounds.GetUpperBoundValue()); } return FReply::Handled(); }
int32 FSequencerTimeSliderController::DrawPlaybackRange(const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FScrubRangeToScreen& RangeToScreen, const FPaintPlaybackRangeArgs& Args) const { if (!TimeSliderArgs.PlaybackRange.IsSet()) { return LayerId; } TRange<float> PlaybackRange = TimeSliderArgs.PlaybackRange.Get(); float PlaybackRangeL = RangeToScreen.InputToLocalX(PlaybackRange.GetLowerBoundValue()) - 1; float PlaybackRangeR = RangeToScreen.InputToLocalX(PlaybackRange.GetUpperBoundValue()) + 1; FSlateDrawElement::MakeBox( OutDrawElements, LayerId+1, AllottedGeometry.ToPaintGeometry(FVector2D(PlaybackRangeL, 0.f), FVector2D(Args.BrushWidth, AllottedGeometry.Size.Y)), Args.StartBrush, MyClippingRect, ESlateDrawEffect::None, FColor(32, 128, 32) // 120, 75, 50 (HSV) ); FSlateDrawElement::MakeBox( OutDrawElements, LayerId+1, AllottedGeometry.ToPaintGeometry(FVector2D(PlaybackRangeR - Args.BrushWidth, 0.f), FVector2D(Args.BrushWidth, AllottedGeometry.Size.Y)), Args.EndBrush, MyClippingRect, ESlateDrawEffect::None, FColor(128, 32, 32) // 0, 75, 50 (HSV) ); // Black tint for excluded regions FSlateDrawElement::MakeBox( OutDrawElements, LayerId+1, AllottedGeometry.ToPaintGeometry(FVector2D(0.f, 0.f), FVector2D(PlaybackRangeL, AllottedGeometry.Size.Y)), FEditorStyle::GetBrush("WhiteBrush"), MyClippingRect, ESlateDrawEffect::None, FLinearColor::Black.CopyWithNewOpacity(0.2f) ); FSlateDrawElement::MakeBox( OutDrawElements, LayerId+1, AllottedGeometry.ToPaintGeometry(FVector2D(PlaybackRangeR, 0.f), FVector2D(AllottedGeometry.Size.X - PlaybackRangeR, AllottedGeometry.Size.Y)), FEditorStyle::GetBrush("WhiteBrush"), MyClippingRect, ESlateDrawEffect::None, FLinearColor::Black.CopyWithNewOpacity(0.2f) ); return LayerId + 1; }
void FSequencerTimeSliderController::PanByDelta( float InDelta ) { TRange<float> LocalViewRange = TimeSliderArgs.ViewRange.Get().GetAnimationTarget(); float CurrentMin = LocalViewRange.GetLowerBoundValue(); float CurrentMax = LocalViewRange.GetUpperBoundValue(); // Adjust the delta to be a percentage of the current range InDelta *= ScrubConstants::ScrollPanFraction * (CurrentMax - CurrentMin); SetViewRange(CurrentMin + InDelta, CurrentMax + InDelta, EViewRangeInterpolation::Animated); }
TOptional<float> FGroupedKeyCollection::FindFirstKeyInRange(const TRange<float>& InRange, EFindKeyDirection Direction) const { // @todo: linear search may be slow where there are lots of keys bool bWithinRange = false; if (Direction == EFindKeyDirection::Backwards) { for (int32 Index = Groups.Num() - 1; Index >= 0; --Index) { if (Groups[Index].RepresentativeTime < InRange.GetUpperBoundValue()) { // Just entered the range return Groups[Index].RepresentativeTime; } else if (InRange.HasLowerBound() && Groups[Index].RepresentativeTime < InRange.GetLowerBoundValue()) { // No longer inside the range return TOptional<float>(); } } } else { for (int32 Index = 0; Index < Groups.Num(); ++Index) { if (Groups[Index].RepresentativeTime > InRange.GetLowerBoundValue()) { // Just entered the range return Groups[Index].RepresentativeTime; } else if (InRange.HasUpperBound() && Groups[Index].RepresentativeTime > InRange.GetUpperBoundValue()) { // No longer inside the range return TOptional<float>(); } } } return TOptional<float>(); }
bool FSequencerTimeSliderController::HitTestPlaybackEnd(const FScrubRangeToScreen& RangeToScreen, const TRange<float>& PlaybackRange, float LocalHitPositionX, float ScrubPosition) const { static float BrushSizeInStateUnits = 6.f, DragToleranceSlateUnits = 2.f; float LocalPlaybackEndPos = RangeToScreen.InputToLocalX(PlaybackRange.GetUpperBoundValue()); // We favor hit testing the scrub bar over hit testing the playback range bounds if (FMath::IsNearlyEqual(LocalPlaybackEndPos, RangeToScreen.InputToLocalX(ScrubPosition), ScrubHandleSize/2.f)) { return false; } // Hit test against the brush region to the left of the playback end position, +/- DragToleranceSlateUnits return LocalHitPositionX >= LocalPlaybackEndPos - BrushSizeInStateUnits - DragToleranceSlateUnits && LocalHitPositionX <= LocalPlaybackEndPos + DragToleranceSlateUnits; }
void ULevelSequencePlayer::Initialize(ULevelSequence* InLevelSequence, UWorld* InWorld, const FLevelSequencePlaybackSettings& Settings) { LevelSequence = InLevelSequence; World = InWorld; PlaybackSettings = Settings; if (UMovieScene* MovieScene = LevelSequence->GetMovieScene()) { TRange<float> PlaybackRange = MovieScene->GetPlaybackRange(); SetPlaybackRange(PlaybackRange.GetLowerBoundValue(), PlaybackRange.GetUpperBoundValue()); } // Ensure everything is set up, ready for playback Stop(); }
FReply FSequencerTimeSliderController::OnMouseWheel( TSharedRef<SWidget> WidgetOwner, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) { if ( TimeSliderArgs.AllowZoom ) { const float ZoomDelta = -0.1f * MouseEvent.GetWheelDelta(); { TRange<float> LocalViewRange = TimeSliderArgs.ViewRange.Get(); float LocalViewRangeMax = LocalViewRange.GetUpperBoundValue(); float LocalViewRangeMin = LocalViewRange.GetLowerBoundValue(); const float OutputViewSize = LocalViewRangeMax - LocalViewRangeMin; const float OutputChange = OutputViewSize * ZoomDelta; float NewViewOutputMin = LocalViewRangeMin - (OutputChange * 0.5f); float NewViewOutputMax = LocalViewRangeMax + (OutputChange * 0.5f); if( FMath::Abs( OutputChange ) > 0.01f && NewViewOutputMin < NewViewOutputMax ) { TOptional<float> LocalClampMin = TimeSliderArgs.ClampMin.Get(); TOptional<float> LocalClampMax = TimeSliderArgs.ClampMax.Get(); // Clamp the range if clamp values are set if ( LocalClampMin.IsSet() && NewViewOutputMin < LocalClampMin.GetValue() ) { NewViewOutputMin = LocalClampMin.GetValue(); } if ( LocalClampMax.IsSet() && NewViewOutputMax > LocalClampMax.GetValue() ) { NewViewOutputMax = LocalClampMax.GetValue(); } TimeSliderArgs.OnViewRangeChanged.ExecuteIfBound(TRange<float>(NewViewOutputMin, NewViewOutputMax)); if( !TimeSliderArgs.ViewRange.IsBound() ) { // The output is not bound to a delegate so we'll manage the value ourselves TimeSliderArgs.ViewRange.Set( TRange<float>( NewViewOutputMin, NewViewOutputMax ) ); } } } return FReply::Handled(); } return FReply::Unhandled(); }
void UMovieSceneShotTrack::AddNewShot(FGuid CameraHandle, UMovieScene& ShotMovieScene, const TRange<float>& TimeRange, const FText& ShotName, int32 ShotNumber ) { Modify(); FName UniqueShotName = MakeUniqueObjectName( this, UMovieSceneShotSection::StaticClass(), *ShotName.ToString() ); UMovieSceneShotSection* NewSection = NewObject<UMovieSceneShotSection>( this, UniqueShotName, RF_Transactional ); NewSection->SetMovieScene( &ShotMovieScene ); NewSection->SetStartTime( TimeRange.GetLowerBoundValue() ); NewSection->SetEndTime( TimeRange.GetUpperBoundValue() ); NewSection->SetCameraGuid( CameraHandle ); NewSection->SetShotNameAndNumber( ShotName , ShotNumber ); SubMovieSceneSections.Add( NewSection ); // When a new shot is added, sort all shots to ensure they are in the correct order SortShots(); }
bool FSequencerTimeSliderController::ZoomByDelta( float InDelta, float MousePositionFraction ) { TRange<float> LocalViewRange = TimeSliderArgs.ViewRange.Get().GetAnimationTarget(); float LocalViewRangeMax = LocalViewRange.GetUpperBoundValue(); float LocalViewRangeMin = LocalViewRange.GetLowerBoundValue(); const float OutputViewSize = LocalViewRangeMax - LocalViewRangeMin; const float OutputChange = OutputViewSize * InDelta; float NewViewOutputMin = LocalViewRangeMin - (OutputChange * MousePositionFraction); float NewViewOutputMax = LocalViewRangeMax + (OutputChange * (1.f - MousePositionFraction)); if( FMath::Abs( OutputChange ) > 0.01f && NewViewOutputMin < NewViewOutputMax ) { SetViewRange(NewViewOutputMin, NewViewOutputMax, EViewRangeInterpolation::Animated); return true; } return false; }
TSharedRef<SWidget> FSequencerTimeSliderController::OpenSetPlaybackRangeMenu(float MouseTime) { const bool bShouldCloseWindowAfterMenuSelection = true; FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, nullptr); FText NumericText; if (SequencerSnapValues::IsTimeSnapIntervalFrameRate(TimeSliderArgs.Settings->GetTimeSnapInterval()) && TimeSliderArgs.Settings->GetShowFrameNumbers()) { NumericText = FText::Format(LOCTEXT("FrameTextFormat", "Playback Range (at frame {0}):"), FText::AsNumber(TimeToFrame(MouseTime))); } else { NumericText = FText::Format(LOCTEXT("TimeTextFormat", "Playback Range (at {0}s):"), FText::AsNumber(MouseTime)); } TRange<float> PlaybackRange = TimeSliderArgs.PlaybackRange.Get(); MenuBuilder.BeginSection("SequencerPlaybackRangeMenu", NumericText); { MenuBuilder.AddMenuEntry( FText::Format(LOCTEXT("SetPlaybackStart", "Set Start Time"), NumericText), FText(), FSlateIcon(), FUIAction( FExecuteAction::CreateLambda([=]{ return SetPlaybackRangeStart(MouseTime); }), FCanExecuteAction::CreateLambda([=]{ return MouseTime <= PlaybackRange.GetUpperBoundValue(); }) ) ); MenuBuilder.AddMenuEntry( FText::Format(LOCTEXT("SetPlaybackEnd", "Set End Time"), NumericText), FText(), FSlateIcon(), FUIAction( FExecuteAction::CreateLambda([=]{ return SetPlaybackRangeEnd(MouseTime); }), FCanExecuteAction::CreateLambda([=]{ return MouseTime >= PlaybackRange.GetLowerBoundValue(); }) ) ); } MenuBuilder.EndSection(); // SequencerPlaybackRangeMenu return MenuBuilder.MakeWidget(); }
void SSequencerTrackArea::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime ) { CachedGeometry = AllottedGeometry; auto SequencerPin = SequencerWidget.Pin(); if (SequencerPin.IsValid()) { SequencerPin->GetEditTool().Tick(AllottedGeometry, InCurrentTime, InDeltaTime); } FVector2D Size = AllottedGeometry.GetLocalSize(); if (!bLockInOutToStartEndRange.Get()) { if (SizeLastFrame.IsSet() && Size.X != SizeLastFrame->X) { // Zoom by the difference in horizontal size const float Difference = Size.X - SizeLastFrame->X; TRange<float> OldRange = TimeSliderController->GetViewRange().GetAnimationTarget(); TimeSliderController->SetViewRange( OldRange.GetLowerBoundValue(), OldRange.GetUpperBoundValue() + (Difference * OldRange.Size<float>() / SizeLastFrame->X), EViewRangeInterpolation::Immediate ); } } SizeLastFrame = Size; for (int32 Index = 0; Index < Children.Num(); ) { if (!StaticCastSharedRef<SWeakWidget>(Children[Index].GetWidget())->ChildWidgetIsValid()) { Children.RemoveAt(Index); } else { ++Index; } } }
void FVisualLoggerTimeSliderController::CommitScrubPosition( float NewValue, bool bIsScrubbing ) { // Manage the scrub position ourselves if its not bound to a delegate if ( !TimeSliderArgs.ScrubPosition.IsBound() ) { TimeSliderArgs.ScrubPosition.Set( NewValue ); } TRange<float> LocalViewRange = TimeSliderArgs.ViewRange.Get(); const float RangeSize = LocalViewRange.Size<float>(); if (NewValue < LocalViewRange.GetLowerBoundValue()) { SetTimeRange(NewValue, NewValue + RangeSize); } else if (NewValue > LocalViewRange.GetUpperBoundValue()) { SetTimeRange(NewValue - RangeSize, NewValue); } TimeSliderArgs.OnScrubPositionChanged.ExecuteIfBound( NewValue, bIsScrubbing ); }
void FVisualLoggerCanvasRenderer::DrawHistogramGraphs(class UCanvas* Canvas, class APlayerController*) { if (FLogVisualizer::Get().GetTimeSliderController().IsValid() == false) { return; } const float GoldenRatioConjugate = 0.618033988749895f; if (CollectedGraphs.Num() > 0) { const FVisualLoggerTimeSliderArgs& TimeSliderArgs = FLogVisualizer::Get().GetTimeSliderController()->GetTimeSliderArgs(); TRange<float> LocalViewRange = TimeSliderArgs.ViewRange.Get(); const float LocalViewRangeMin = LocalViewRange.GetLowerBoundValue(); const float LocalViewRangeMax = LocalViewRange.GetUpperBoundValue(); const float LocalSequenceLength = LocalViewRangeMax - LocalViewRangeMin; const float WindowHalfWidth = LocalSequenceLength * TimeSliderArgs.CursorSize.Get() * 0.5f; const FVector2D TimeStampWindow(SelectedEntry.TimeStamp - WindowHalfWidth, SelectedEntry.TimeStamp + WindowHalfWidth); const FColor GraphsBackgroundColor = ULogVisualizerSettings::StaticClass()->GetDefaultObject<ULogVisualizerSettings>()->GraphsBackgroundColor; const int NumberOfGraphs = CollectedGraphs.Num(); const int32 NumberOfColumns = FMath::CeilToInt(FMath::Sqrt(NumberOfGraphs)); int32 NumberOfRows = FMath::FloorToInt(NumberOfGraphs / NumberOfColumns); if (NumberOfGraphs - NumberOfRows * NumberOfColumns > 0) { NumberOfRows += 1; } const int32 MaxNumberOfGraphs = FMath::Max(NumberOfRows, NumberOfColumns); const float GraphWidth = 0.8f / NumberOfColumns; const float GraphHeight = 0.8f / NumberOfRows; const float XGraphSpacing = 0.2f / (MaxNumberOfGraphs + 1); const float YGraphSpacing = 0.2f / (MaxNumberOfGraphs + 1); const float StartX = XGraphSpacing; float StartY = 0.5 + (0.5 * NumberOfRows - 1) * (GraphHeight + YGraphSpacing); float CurrentX = StartX; float CurrentY = StartY; int32 GraphIndex = 0; int32 CurrentColumn = 0; int32 CurrentRow = 0; bool bDrawExtremesOnGraphs = ULogVisualizerSettings::StaticClass()->GetDefaultObject<ULogVisualizerSettings>()->bDrawExtremesOnGraphs; for (auto It(CollectedGraphs.CreateIterator()); It; ++It) { TWeakObjectPtr<UReporterGraph> HistogramGraph = Canvas->GetReporterGraph(); if (!HistogramGraph.IsValid()) { break; } HistogramGraph->SetNumGraphLines(It->Value.GraphLines.Num()); int32 LineIndex = 0; UFont* Font = GEngine->GetSmallFont(); int32 MaxStringSize = 0; float Hue = 0; auto& CategoriesForGraph = UsedGraphCategories.FindOrAdd(It->Key.ToString()); It->Value.GraphLines.KeySort(TLess<FName>()); for (auto LinesIt(It->Value.GraphLines.CreateConstIterator()); LinesIt; ++LinesIt) { const FString DataName = LinesIt->Value.DataName.ToString(); int32 CategoryIndex = CategoriesForGraph.Find(DataName); if (CategoryIndex == INDEX_NONE) { CategoryIndex = CategoriesForGraph.AddUnique(DataName); } Hue = CategoryIndex * GoldenRatioConjugate; if (Hue > 1) { Hue -= FMath::FloorToFloat(Hue); } HistogramGraph->GetGraphLine(LineIndex)->Color = FLinearColor::FGetHSV(Hue * 255, 0, 244); HistogramGraph->GetGraphLine(LineIndex)->LineName = DataName; HistogramGraph->GetGraphLine(LineIndex)->Data.Append(LinesIt->Value.Samples); HistogramGraph->GetGraphLine(LineIndex)->LeftExtreme = LinesIt->Value.LeftExtreme; HistogramGraph->GetGraphLine(LineIndex)->RightExtreme = LinesIt->Value.RightExtreme; int32 DummyY, StringSizeX; StringSize(Font, StringSizeX, DummyY, *LinesIt->Value.DataName.ToString()); MaxStringSize = StringSizeX > MaxStringSize ? StringSizeX : MaxStringSize; ++LineIndex; } FVector2D GraphSpaceSize; GraphSpaceSize.Y = GraphSpaceSize.X = 0.8f / CollectedGraphs.Num(); HistogramGraph->SetGraphScreenSize(CurrentX, CurrentX + GraphWidth, CurrentY, CurrentY + GraphHeight); CurrentX += GraphWidth + XGraphSpacing; HistogramGraph->SetAxesMinMax(FVector2D(TimeStampWindow.X, It->Value.Min.Y), FVector2D(TimeStampWindow.Y, It->Value.Max.Y)); HistogramGraph->DrawCursorOnGraph(true); HistogramGraph->UseTinyFont(CollectedGraphs.Num() >= 5); HistogramGraph->SetCursorLocation(SelectedEntry.TimeStamp); HistogramGraph->SetNumThresholds(0); HistogramGraph->SetStyles(EGraphAxisStyle::Grid, EGraphDataStyle::Lines); HistogramGraph->SetBackgroundColor(GraphsBackgroundColor); HistogramGraph->SetLegendPosition(/*bShowHistogramLabelsOutside*/ false ? ELegendPosition::Outside : ELegendPosition::Inside); HistogramGraph->OffsetDataSets(/*bOffsetDataSet*/false); HistogramGraph->DrawExtremesOnGraph(bDrawExtremesOnGraphs); HistogramGraph->bVisible = true; HistogramGraph->Draw(Canvas); ++GraphIndex; if (++CurrentColumn >= NumberOfColumns) { CurrentColumn = 0; CurrentRow++; CurrentX = StartX; CurrentY -= GraphHeight + YGraphSpacing; } } } }
void FMoveSection::OnDrag( const FPointerEvent& MouseEvent, const FVector2D& LocalMousePos, const FTimeToPixel& TimeToPixelConverter, TSharedPtr<FTrackNode> SequencerNode ) { if( Section.IsValid() ) { auto Sections = SequencerNode->GetSections(); TArray<UMovieSceneSection*> MovieSceneSections; for (int32 i = 0; i < Sections.Num(); ++i) { MovieSceneSections.Add(Sections[i]->GetSectionObject()); } FVector2D TotalDelta = LocalMousePos - DragOffset; float DistanceMoved = TotalDelta.X / TimeToPixelConverter.GetPixelsPerInput(); float DeltaTime = DistanceMoved; if ( Settings->GetIsSnapEnabled() ) { bool bSnappedToSection = false; if ( Settings->GetSnapSectionTimesToSections() ) { TArray<float> TimesToSnapTo; GetSectionSnapTimes(TimesToSnapTo, Section.Get(), SequencerNode, true); TArray<float> TimesToSnap; TimesToSnap.Add(DistanceMoved + Section->GetStartTime()); TimesToSnap.Add(DistanceMoved + Section->GetEndTime()); float OutSnappedTime = 0.f; float OutNewTime = 0.f; if (SnapToTimes(TimesToSnap, TimesToSnapTo, TimeToPixelConverter, OutSnappedTime, OutNewTime)) { DeltaTime = OutNewTime - (OutSnappedTime - DistanceMoved); bSnappedToSection = true; } } if ( bSnappedToSection == false && Settings->GetSnapSectionTimesToInterval() ) { float NewStartTime = DistanceMoved + Section->GetStartTime(); DeltaTime = Settings->SnapTimeToInterval( NewStartTime ) - Section->GetStartTime(); } } int32 TargetRowIndex = Section->GetRowIndex(); // vertical dragging - master tracks only if (SequencerNode->GetTrack()->SupportsMultipleRows() && Sections.Num() > 1) { float TrackHeight = Sections[0]->GetSectionHeight(); if (LocalMousePos.Y < 0.f || LocalMousePos.Y > TrackHeight) { int32 MaxRowIndex = 0; for (int32 i = 0; i < Sections.Num(); ++i) { if (Sections[i]->GetSectionObject() != Section.Get()) { MaxRowIndex = FMath::Max(MaxRowIndex, Sections[i]->GetSectionObject()->GetRowIndex()); } } TargetRowIndex = FMath::Clamp(Section->GetRowIndex() + FMath::FloorToInt(LocalMousePos.Y / TrackHeight), 0, MaxRowIndex + 1); } } bool bDeltaX = !FMath::IsNearlyZero(TotalDelta.X); bool bDeltaY = TargetRowIndex != Section->GetRowIndex(); if (bDeltaX && bDeltaY && !Section->OverlapsWithSections(MovieSceneSections, TargetRowIndex - Section->GetRowIndex(), DeltaTime)) { Section->MoveSection(DeltaTime, DraggedKeyHandles); Section->SetRowIndex(TargetRowIndex); } else { if (bDeltaY && !Section->OverlapsWithSections(MovieSceneSections, TargetRowIndex - Section->GetRowIndex(), 0.f)) { Section->SetRowIndex(TargetRowIndex); } if (bDeltaX) { if (!Section->OverlapsWithSections(MovieSceneSections, 0, DeltaTime)) { Section->MoveSection(DeltaTime, DraggedKeyHandles); } else { // Find the borders of where you can move to TRange<float> SectionBoundaries = GetSectionBoundaries(Section.Get(), SequencerNode); float LeftMovementMaximum = SectionBoundaries.GetLowerBoundValue() - Section->GetStartTime(); float RightMovementMaximum = SectionBoundaries.GetUpperBoundValue() - Section->GetEndTime(); // Tell the section to move itself and any data it has Section->MoveSection( FMath::Clamp(DeltaTime, LeftMovementMaximum, RightMovementMaximum), DraggedKeyHandles ); } } } } }
FReply FSequencerTimeSliderController::OnMouseMove( SWidget& WidgetOwner, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) { if ( WidgetOwner.HasMouseCapture() ) { if (MouseEvent.IsMouseButtonDown( EKeys::RightMouseButton )) { if (!bPanning) { DistanceDragged += FMath::Abs( MouseEvent.GetCursorDelta().X ); if ( DistanceDragged > FSlateApplication::Get().GetDragTriggerDistance() ) { bPanning = true; } } else { TRange<float> LocalViewRange = TimeSliderArgs.ViewRange.Get(); float LocalViewRangeMin = LocalViewRange.GetLowerBoundValue(); float LocalViewRangeMax = LocalViewRange.GetUpperBoundValue(); FScrubRangeToScreen ScaleInfo( LocalViewRange, MyGeometry.Size ); FVector2D ScreenDelta = MouseEvent.GetCursorDelta(); FVector2D InputDelta; InputDelta.X = ScreenDelta.X/ScaleInfo.PixelsPerInput; float NewViewOutputMin = LocalViewRangeMin - InputDelta.X; float NewViewOutputMax = LocalViewRangeMax - InputDelta.X; ClampViewRange(NewViewOutputMin, NewViewOutputMax); SetViewRange(NewViewOutputMin, NewViewOutputMax, EViewRangeInterpolation::Immediate); } } else if (MouseEvent.IsMouseButtonDown( EKeys::LeftMouseButton )) { TRange<float> LocalViewRange = TimeSliderArgs.ViewRange.Get(); DistanceDragged += FMath::Abs( MouseEvent.GetCursorDelta().X ); if ( MouseDragType == DRAG_NONE ) { if ( DistanceDragged > FSlateApplication::Get().GetDragTriggerDistance() ) { FScrubRangeToScreen RangeToScreen(LocalViewRange, MyGeometry.Size); const float ScrubPosition = TimeSliderArgs.ScrubPosition.Get(); TRange<float> PlaybackRange = TimeSliderArgs.PlaybackRange.Get(); float LocalMouseDownPos = RangeToScreen.InputToLocalX(MouseDownRange[0]); // Favor dragging the end position if (HitTestPlaybackEnd(RangeToScreen, PlaybackRange, LocalMouseDownPos, ScrubPosition)) { MouseDragType = DRAG_END_RANGE; TimeSliderArgs.OnBeginPlaybackRangeDrag.ExecuteIfBound(); } else if (HitTestPlaybackStart(RangeToScreen, PlaybackRange, LocalMouseDownPos, ScrubPosition)) { MouseDragType = DRAG_START_RANGE; TimeSliderArgs.OnBeginPlaybackRangeDrag.ExecuteIfBound(); } else if (FSlateApplication::Get().GetModifierKeys().AreModifersDown(EModifierKey::Control)) { MouseDragType = DRAG_SETTING_RANGE; } else { MouseDragType = DRAG_SCRUBBING_TIME; TimeSliderArgs.OnBeginScrubberMovement.ExecuteIfBound(); } } } else { FScrubRangeToScreen RangeToScreen( TimeSliderArgs.ViewRange.Get(), MyGeometry.Size ); FVector2D CursorPos = MyGeometry.AbsoluteToLocal( MouseEvent.GetLastScreenSpacePosition() ); float NewValue = RangeToScreen.LocalXToInput( CursorPos.X ); // Set the start range time? if (MouseDragType == DRAG_START_RANGE) { if (TimeSliderArgs.Settings->GetIsSnapEnabled()) { NewValue = TimeSliderArgs.Settings->SnapTimeToInterval(NewValue); } SetPlaybackRangeStart(NewValue); } // Set the end range time? else if(MouseDragType == DRAG_END_RANGE) { if (TimeSliderArgs.Settings->GetIsSnapEnabled()) { NewValue = TimeSliderArgs.Settings->SnapTimeToInterval(NewValue); } SetPlaybackRangeEnd(NewValue); } else if (MouseDragType == DRAG_SCRUBBING_TIME) { if ( TimeSliderArgs.Settings->GetIsSnapEnabled() && TimeSliderArgs.Settings->GetSnapPlayTimeToInterval() ) { NewValue = TimeSliderArgs.Settings->SnapTimeToInterval(NewValue); } // Delegate responsibility for clamping to the current viewrange to the client CommitScrubPosition( NewValue, /*bIsScrubbing=*/true ); } else if (MouseDragType == DRAG_SETTING_RANGE) { MouseDownRange[1] = NewValue; } } } return FReply::Handled(); } return FReply::Unhandled(); }
FReply FSequencerTimeSliderController::OnMouseMove( TSharedRef<SWidget> WidgetOwner, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) { if ( WidgetOwner->HasMouseCapture() ) { if (MouseEvent.IsMouseButtonDown( EKeys::RightMouseButton )) { if (!bPanning) { DistanceDragged += FMath::Abs( MouseEvent.GetCursorDelta().X ); if ( DistanceDragged > FSlateApplication::Get().GetDragTriggerDistance() ) { bPanning = true; } } else { TRange<float> LocalViewRange = TimeSliderArgs.ViewRange.Get(); float LocalViewRangeMin = LocalViewRange.GetLowerBoundValue(); float LocalViewRangeMax = LocalViewRange.GetUpperBoundValue(); FScrubRangeToScreen ScaleInfo( LocalViewRange, MyGeometry.Size ); FVector2D ScreenDelta = MouseEvent.GetCursorDelta(); FVector2D InputDelta; InputDelta.X = ScreenDelta.X/ScaleInfo.PixelsPerInput; float NewViewOutputMin = LocalViewRangeMin - InputDelta.X; float NewViewOutputMax = LocalViewRangeMax - InputDelta.X; SetViewRange(NewViewOutputMin, NewViewOutputMax, EViewRangeInterpolation::Immediate); } } else if (MouseEvent.IsMouseButtonDown( EKeys::LeftMouseButton )) { if ( !bDraggingScrubber ) { DistanceDragged += FMath::Abs( MouseEvent.GetCursorDelta().X ); if ( DistanceDragged > FSlateApplication::Get().GetDragTriggerDistance() ) { bDraggingScrubber = true; TimeSliderArgs.OnBeginScrubberMovement.ExecuteIfBound(); } } else { FScrubRangeToScreen RangeToScreen( TimeSliderArgs.ViewRange.Get(), MyGeometry.Size ); FVector2D CursorPos = MyGeometry.AbsoluteToLocal( MouseEvent.GetLastScreenSpacePosition() ); float NewValue = RangeToScreen.LocalXToInput( CursorPos.X ); const USequencerSettings* Settings = GetDefault<USequencerSettings>(); if ( Settings->GetIsSnapEnabled() && Settings->GetSnapPlayTimeToInterval() ) { NewValue = Settings->SnapTimeToInterval(NewValue); } CommitScrubPosition( NewValue, /*bIsScrubbing=*/true ); } } return FReply::Handled(); } return FReply::Unhandled(); }
int32 FSequencerTimeSliderController::OnPaintTimeSlider( bool bMirrorLabels, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const { const bool bEnabled = bParentEnabled; const ESlateDrawEffect::Type DrawEffects = bEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect; TRange<float> LocalViewRange = TimeSliderArgs.ViewRange.Get(); const float LocalViewRangeMin = LocalViewRange.GetLowerBoundValue(); const float LocalViewRangeMax = LocalViewRange.GetUpperBoundValue(); const float LocalSequenceLength = LocalViewRangeMax-LocalViewRangeMin; FVector2D Scale = FVector2D(1.0f,1.0f); if ( LocalSequenceLength > 0) { FScrubRangeToScreen RangeToScreen( LocalViewRange, AllottedGeometry.Size ); const float MajorTickHeight = 9.0f; FDrawTickArgs Args; Args.AllottedGeometry = AllottedGeometry; Args.bMirrorLabels = bMirrorLabels; Args.bOnlyDrawMajorTicks = false; Args.TickColor = FLinearColor::White; Args.ClippingRect = MyClippingRect; Args.DrawEffects = DrawEffects; Args.StartLayer = LayerId; Args.TickOffset = bMirrorLabels ? 0.0f : FMath::Abs( AllottedGeometry.Size.Y - MajorTickHeight ); Args.MajorTickHeight = MajorTickHeight; DrawTicks( OutDrawElements, RangeToScreen, Args ); FPaintPlaybackRangeArgs PlaybackRangeArgs( bMirrorLabels ? FEditorStyle::GetBrush("Sequencer.Timeline.PlayRange_Bottom_L") : FEditorStyle::GetBrush("Sequencer.Timeline.PlayRange_Top_L"), bMirrorLabels ? FEditorStyle::GetBrush("Sequencer.Timeline.PlayRange_Bottom_R") : FEditorStyle::GetBrush("Sequencer.Timeline.PlayRange_Top_R"), 6.f ); LayerId = DrawPlaybackRange(AllottedGeometry, MyClippingRect, OutDrawElements, LayerId, RangeToScreen, PlaybackRangeArgs); float HalfSize = FMath::CeilToFloat(ScrubHandleSize/2.0f); // Draw the scrub handle float XPos = RangeToScreen.InputToLocalX( TimeSliderArgs.ScrubPosition.Get() ); // Should draw above the text const int32 ArrowLayer = LayerId + 2; FPaintGeometry MyGeometry = AllottedGeometry.ToPaintGeometry( FVector2D( XPos-HalfSize, 0 ), FVector2D( ScrubHandleSize, AllottedGeometry.Size.Y ) ); FLinearColor ScrubColor = InWidgetStyle.GetColorAndOpacityTint(); // @todo Sequencer this color should be specified in the style ScrubColor.A = ScrubColor.A*0.75f; ScrubColor.B *= 0.1f; ScrubColor.G *= 0.2f; FSlateDrawElement::MakeBox( OutDrawElements, ArrowLayer, MyGeometry, bMirrorLabels ? ScrubHandleUp : ScrubHandleDown, MyClippingRect, DrawEffects, ScrubColor ); // Draw the current time next to the scrub handle float Time = TimeSliderArgs.ScrubPosition.Get(); FString FrameString; if (SequencerSnapValues::IsTimeSnapIntervalFrameRate(TimeSliderArgs.Settings->GetTimeSnapInterval()) && TimeSliderArgs.Settings->GetShowFrameNumbers()) { float FrameRate = 1.0f/TimeSliderArgs.Settings->GetTimeSnapInterval(); float FrameTime = Time * FrameRate; int32 Frame = SequencerHelpers::TimeToFrame(Time, FrameRate); const float FrameTolerance = 0.001f; if (FMath::IsNearlyEqual(FrameTime, (float)Frame, FrameTolerance)) { FrameString = FString::Printf( TEXT("%d"), TimeToFrame(Time)); } else { FrameString = FString::Printf( TEXT("%.3f"), FrameTime); } } else { FrameString = FString::Printf( TEXT("%.2f"), Time ); } FSlateFontInfo SmallLayoutFont( FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Regular.ttf"), 10 ); const TSharedRef< FSlateFontMeasure > FontMeasureService = FSlateApplication::Get().GetRenderer()->GetFontMeasureService(); FVector2D TextSize = FontMeasureService->Measure(FrameString, SmallLayoutFont); // Flip the text position if getting near the end of the view range if ((AllottedGeometry.Size.X - XPos) < (TextSize.X + 14.f)) { XPos = XPos - TextSize.X - 12.f; } else { XPos = XPos + 10.f; } FVector2D TextOffset( XPos, Args.bMirrorLabels ? TextSize.Y-6.f : Args.AllottedGeometry.Size.Y - (Args.MajorTickHeight+TextSize.Y) ); FSlateDrawElement::MakeText( OutDrawElements, Args.StartLayer+1, Args.AllottedGeometry.ToPaintGeometry( TextOffset, TextSize ), FrameString, SmallLayoutFont, Args.ClippingRect, Args.DrawEffects, Args.TickColor ); if (MouseDragType == DRAG_SETTING_RANGE) { float MouseStartPosX = RangeToScreen.InputToLocalX(MouseDownRange[0]); float MouseEndPosX = RangeToScreen.InputToLocalX(MouseDownRange[1]); float RangePosX = MouseStartPosX < MouseEndPosX ? MouseStartPosX : MouseEndPosX; float RangeSizeX = FMath::Abs(MouseStartPosX - MouseEndPosX); FSlateDrawElement::MakeBox( OutDrawElements, LayerId+1, AllottedGeometry.ToPaintGeometry( FVector2D(RangePosX, 0.f), FVector2D(RangeSizeX, AllottedGeometry.Size.Y) ), bMirrorLabels ? ScrubHandleDown : ScrubHandleUp, MyClippingRect, DrawEffects, MouseStartPosX < MouseEndPosX ? FLinearColor(0.5f, 0.5f, 0.5f) : FLinearColor(0.25f, 0.3f, 0.3f) ); } return ArrowLayer; } return LayerId; }
FReply FVisualLoggerTimeSliderController::OnMouseWheel( TSharedRef<SWidget> WidgetOwner, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) { FReply ReturnValue = FReply::Unhandled();; if (MouseEvent.IsLeftShiftDown()) { const float ZoomDelta = 0.025f * MouseEvent.GetWheelDelta(); TimeSliderArgs.CursorSize.Set(FMath::Clamp(TimeSliderArgs.CursorSize.Get() + ZoomDelta, 0.0f, 1.0f)); ReturnValue = FReply::Handled(); } else if ( TimeSliderArgs.AllowZoom ) { const float ZoomDelta = -0.1f * MouseEvent.GetWheelDelta(); { TRange<float> LocalViewRange = TimeSliderArgs.ViewRange.Get(); float LocalViewRangeMax = LocalViewRange.GetUpperBoundValue(); float LocalViewRangeMin = LocalViewRange.GetLowerBoundValue(); const float OutputViewSize = LocalViewRangeMax - LocalViewRangeMin; const float OutputChange = OutputViewSize * ZoomDelta; float NewViewOutputMin = LocalViewRangeMin - (OutputChange * 0.5f); float NewViewOutputMax = LocalViewRangeMax + (OutputChange * 0.5f); if( FMath::Abs( OutputChange ) > 0.01f && NewViewOutputMin < NewViewOutputMax ) { float LocalClampMin = TimeSliderArgs.ClampRange.Get().GetLowerBoundValue(); float LocalClampMax = TimeSliderArgs.ClampRange.Get().GetUpperBoundValue(); // Clamp the range if clamp values are set if ( NewViewOutputMin < LocalClampMin ) { NewViewOutputMin = LocalClampMin; } if ( NewViewOutputMax > LocalClampMax ) { NewViewOutputMax = LocalClampMax; } TimeSliderArgs.OnViewRangeChanged.ExecuteIfBound(TRange<float>(NewViewOutputMin, NewViewOutputMax), EViewRangeInterpolation::Immediate, false); if (Scrollbar.IsValid()) { float InOffsetFraction = (NewViewOutputMin - LocalClampMin) / (LocalClampMax - LocalClampMin); float InThumbSizeFraction = (NewViewOutputMax - NewViewOutputMin) / (LocalClampMax - LocalClampMin); Scrollbar->SetState(InOffsetFraction, InThumbSizeFraction); } if( !TimeSliderArgs.ViewRange.IsBound() ) { // The output is not bound to a delegate so we'll manage the value ourselves TimeSliderArgs.ViewRange.Set( TRange<float>( NewViewOutputMin, NewViewOutputMax ) ); } } } ReturnValue = FReply::Handled(); } return ReturnValue; }
FReply FSequencerTimeSliderController::OnMouseMove( TSharedRef<SWidget> WidgetOwner, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) { if ( WidgetOwner->HasMouseCapture() ) { if (MouseEvent.IsMouseButtonDown( EKeys::RightMouseButton )) { if (!bPanning) { DistanceDragged += FMath::Abs( MouseEvent.GetCursorDelta().X ); if ( DistanceDragged > FSlateApplication::Get().GetDragTriggerDistnace() ) { bPanning = true; } } else { TRange<float> LocalViewRange = TimeSliderArgs.ViewRange.Get(); float LocalViewRangeMin = LocalViewRange.GetLowerBoundValue(); float LocalViewRangeMax = LocalViewRange.GetUpperBoundValue(); FScrubRangeToScreen ScaleInfo( LocalViewRange, MyGeometry.Size ); FVector2D ScreenDelta = MouseEvent.GetCursorDelta(); FVector2D InputDelta; InputDelta.X = ScreenDelta.X/ScaleInfo.PixelsPerInput; float NewViewOutputMin = LocalViewRangeMin - InputDelta.X; float NewViewOutputMax = LocalViewRangeMax - InputDelta.X; TOptional<float> LocalClampMin = TimeSliderArgs.ClampMin.Get(); TOptional<float> LocalClampMax = TimeSliderArgs.ClampMax.Get(); // Clamp the range if clamp values are set if ( LocalClampMin.IsSet() && NewViewOutputMin < LocalClampMin.GetValue() ) { NewViewOutputMin = LocalClampMin.GetValue(); } if ( LocalClampMax.IsSet() && NewViewOutputMax > LocalClampMax.GetValue() ) { NewViewOutputMax = LocalClampMax.GetValue(); } TimeSliderArgs.OnViewRangeChanged.ExecuteIfBound(TRange<float>(NewViewOutputMin, NewViewOutputMax)); if( !TimeSliderArgs.ViewRange.IsBound() ) { // The output is not bound to a delegate so we'll manage the value ourselves TimeSliderArgs.ViewRange.Set( TRange<float>( NewViewOutputMin, NewViewOutputMax ) ); } } } else if (MouseEvent.IsMouseButtonDown( EKeys::LeftMouseButton )) { if ( !bDraggingScrubber ) { DistanceDragged += FMath::Abs( MouseEvent.GetCursorDelta().X ); if ( DistanceDragged > FSlateApplication::Get().GetDragTriggerDistnace() ) { bDraggingScrubber = true; TimeSliderArgs.OnBeginScrubberMovement.ExecuteIfBound(); } } else { FScrubRangeToScreen RangeToScreen( TimeSliderArgs.ViewRange.Get(), MyGeometry.Size ); FVector2D CursorPos = MyGeometry.AbsoluteToLocal( MouseEvent.GetLastScreenSpacePosition() ); float NewValue = RangeToScreen.LocalXToInput( CursorPos.X ); const USequencerSnapSettings* SnapSettings = GetDefault<USequencerSnapSettings>(); if ( SnapSettings->GetIsSnapEnabled() && SnapSettings->GetSnapPlayTimeToInterval() ) { NewValue = SnapSettings->SnapToInterval(NewValue); } CommitScrubPosition( NewValue, /*bIsScrubbing=*/true ); } } return FReply::Handled(); } return FReply::Unhandled(); }
FReply FVisualLoggerTimeSliderController::OnMouseMove( TSharedRef<SWidget> WidgetOwner, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) { if ( WidgetOwner->HasMouseCapture() ) { if (MouseEvent.IsMouseButtonDown(EKeys::RightMouseButton)) { if (!bPanning) { DistanceDragged += FMath::Abs( MouseEvent.GetCursorDelta().X ); if ( DistanceDragged > FSlateApplication::Get().GetDragTriggerDistance() ) { FReply::Handled().CaptureMouse(WidgetOwner).UseHighPrecisionMouseMovement(WidgetOwner); SoftwareCursorPosition = MyGeometry.AbsoluteToLocal(MouseEvent.GetLastScreenSpacePosition()); bPanning = true; } } else { SoftwareCursorPosition = MyGeometry.AbsoluteToLocal(MouseEvent.GetLastScreenSpacePosition()); TRange<float> LocalViewRange = TimeSliderArgs.ViewRange.Get(); float LocalViewRangeMin = LocalViewRange.GetLowerBoundValue(); float LocalViewRangeMax = LocalViewRange.GetUpperBoundValue(); FScrubRangeToScreen ScaleInfo( LocalViewRange, MyGeometry.Size ); FVector2D ScreenDelta = MouseEvent.GetCursorDelta(); FVector2D InputDelta; InputDelta.X = ScreenDelta.X/ScaleInfo.PixelsPerInput; float NewViewOutputMin = LocalViewRangeMin - InputDelta.X; float NewViewOutputMax = LocalViewRangeMax - InputDelta.X; float LocalClampMin = TimeSliderArgs.ClampRange.Get().GetLowerBoundValue(); float LocalClampMax = TimeSliderArgs.ClampRange.Get().GetUpperBoundValue(); // Clamp the range if ( NewViewOutputMin < LocalClampMin ) { NewViewOutputMin = LocalClampMin; } if ( NewViewOutputMax > LocalClampMax ) { NewViewOutputMax = LocalClampMax; } TimeSliderArgs.OnViewRangeChanged.ExecuteIfBound(TRange<float>(NewViewOutputMin, NewViewOutputMax), EViewRangeInterpolation::Immediate, false); if (Scrollbar.IsValid()) { float InOffsetFraction = (NewViewOutputMin - LocalClampMin) / (LocalClampMax - LocalClampMin); float InThumbSizeFraction = (NewViewOutputMax - NewViewOutputMin) / (LocalClampMax - LocalClampMin); Scrollbar->SetState(InOffsetFraction, InThumbSizeFraction); } if( !TimeSliderArgs.ViewRange.IsBound() ) { // The output is not bound to a delegate so we'll manage the value ourselves TimeSliderArgs.ViewRange.Set( TRange<float>( NewViewOutputMin, NewViewOutputMax ) ); } } } else if (MouseEvent.IsMouseButtonDown( EKeys::LeftMouseButton )) { if ( !bDraggingScrubber ) { DistanceDragged += FMath::Abs( MouseEvent.GetCursorDelta().X ); if ( DistanceDragged > 0/*FSlateApplication::Get().GetDragTriggerDistance()*/ ) { bDraggingScrubber = true; TimeSliderArgs.OnBeginScrubberMovement.ExecuteIfBound(); } } else { FScrubRangeToScreen RangeToScreen( TimeSliderArgs.ViewRange.Get(), MyGeometry.Size ); FVector2D CursorPos = MyGeometry.AbsoluteToLocal( MouseEvent.GetLastScreenSpacePosition() ); float NewValue = RangeToScreen.LocalXToInput( CursorPos.X ); float LocalClampMin = TimeSliderArgs.ClampRange.Get().GetLowerBoundValue(); float LocalClampMax = TimeSliderArgs.ClampRange.Get().GetUpperBoundValue(); if (NewValue < LocalClampMin) { NewValue = LocalClampMin; } if (NewValue > LocalClampMax) { NewValue = LocalClampMax; } CommitScrubPosition(NewValue, /*bIsScrubbing=*/true); } } return FReply::Handled(); } return FReply::Unhandled(); }
void FResizeSection::OnDrag( const FPointerEvent& MouseEvent, const FVector2D& LocalMousePos, const FTimeToPixel& TimeToPixelConverter, TSharedPtr<FTrackNode> SequencerNode ) { bool bIsDilating = MouseEvent.IsControlDown(); if( Section.IsValid() ) { // Convert the current mouse position to a time float NewTime = TimeToPixelConverter.PixelToTime( LocalMousePos.X ); // Find the borders of where you can drag to TRange<float> SectionBoundaries = GetSectionBoundaries(Section.Get(), SequencerNode); // Snapping if ( Settings->GetIsSnapEnabled() ) { bool bSnappedToSection = false; if ( Settings->GetSnapSectionTimesToSections() ) { TArray<float> TimesToSnapTo; GetSectionSnapTimes(TimesToSnapTo, Section.Get(), SequencerNode, bIsDilating); TOptional<float> NewSnappedTime = SnapToTimes(NewTime, TimesToSnapTo, TimeToPixelConverter); if (NewSnappedTime.IsSet()) { NewTime = NewSnappedTime.GetValue(); bSnappedToSection = true; } } if ( bSnappedToSection == false && Settings->GetSnapSectionTimesToInterval() ) { NewTime = Settings->SnapTimeToInterval(NewTime); } } if( bDraggingByEnd ) { // Dragging the end of a section // Ensure we aren't shrinking past the start time NewTime = FMath::Clamp( NewTime, Section->GetStartTime(), SectionBoundaries.GetUpperBoundValue() ); if (bIsDilating) { float NewSize = NewTime - Section->GetStartTime(); float DilationFactor = NewSize / Section->GetTimeSize(); Section->DilateSection(DilationFactor, Section->GetStartTime(), DraggedKeyHandles); } else { Section->SetEndTime( NewTime ); } } else if( !bDraggingByEnd ) { // Dragging the start of a section // Ensure we arent expanding past the end time NewTime = FMath::Clamp( NewTime, SectionBoundaries.GetLowerBoundValue(), Section->GetEndTime() ); if (bIsDilating) { float NewSize = Section->GetEndTime() - NewTime; float DilationFactor = NewSize / Section->GetTimeSize(); Section->DilateSection(DilationFactor, Section->GetEndTime(), DraggedKeyHandles); } else { Section->SetStartTime( NewTime ); } } } }
void FVisualLoggerCanvasRenderer::DrawOnCanvas(class UCanvas* Canvas, class APlayerController*) { if (GEngine == NULL) { return; } UWorld* World = FLogVisualizer::Get().GetWorld(); if (World == NULL) { return; } UFont* Font = GEngine->GetSmallFont(); FCanvasTextItem TextItem(FVector2D::ZeroVector, FText::GetEmpty(), Font, FLinearColor::White); const FString TimeStampString = FString::Printf(TEXT("%.2f"), SelectedEntry.TimeStamp); LogVisualizer::DrawTextShadowed(Canvas, Font, TimeStampString, SelectedEntry.Location); if (bDirtyData && FLogVisualizer::Get().GetTimeSliderController().IsValid()) { const FVisualLoggerTimeSliderArgs& TimeSliderArgs = FLogVisualizer::Get().GetTimeSliderController()->GetTimeSliderArgs(); TRange<float> LocalViewRange = TimeSliderArgs.ViewRange.Get(); const float LocalViewRangeMin = LocalViewRange.GetLowerBoundValue(); const float LocalViewRangeMax = LocalViewRange.GetUpperBoundValue(); const float LocalSequenceLength = LocalViewRangeMax - LocalViewRangeMin; const float WindowHalfWidth = LocalSequenceLength * TimeSliderArgs.CursorSize.Get() * 0.5f; const FVector2D TimeStampWindow(SelectedEntry.TimeStamp - WindowHalfWidth, SelectedEntry.TimeStamp + WindowHalfWidth); CollectedGraphs.Reset(); const TArray<FName>& RowNames = FVisualLoggerDatabase::Get().GetSelectedRows(); for (auto RowName : RowNames) { if (FVisualLoggerDatabase::Get().IsRowVisible(RowName) == false) { continue; } const TArray<FVisualLoggerGraph>& AllGraphs = FVisualLoggerGraphsDatabase::Get().GetGraphsByOwnerName(RowName); for (const FVisualLoggerGraph& CurrentGraph : AllGraphs) { const FName GraphName = CurrentGraph.GetGraphName(); const FName OwnerName = CurrentGraph.GetOwnerName(); if (FVisualLoggerGraphsDatabase::Get().IsGraphVisible(OwnerName, GraphName) == false) { continue; } for (auto DataIt(CurrentGraph.GetConstDataIterator()); DataIt; ++DataIt) { const FVisualLoggerGraphData& GraphData = *DataIt; const bool bIsGraphDataDisabled = FVisualLoggerFilters::Get().IsGraphDataDisabled(GraphName, GraphData.DataName); if (bIsGraphDataDisabled) { continue; } const FName FullGraphName = *FString::Printf(TEXT("%s$%s"), *RowName.ToString(), *GraphName.ToString()); FGraphData &CollectedGraphData = CollectedGraphs.FindOrAdd(FullGraphName); FGraphLineData &LineData = CollectedGraphData.GraphLines.FindOrAdd(GraphData.DataName); LineData.DataName = GraphData.DataName; LineData.Samples = GraphData.Samples; LineData.LeftExtreme = FVector2D::ZeroVector; LineData.RightExtreme = FVector2D::ZeroVector; int32 LeftSideOutsideIndex = INDEX_NONE; int32 RightSideOutsideIndex = INDEX_NONE; for (int32 SampleIndex = 0; SampleIndex < GraphData.Samples.Num(); SampleIndex++) { const FVector2D& SampleValue = GraphData.Samples[SampleIndex]; CollectedGraphData.Min.X = FMath::Min(CollectedGraphData.Min.X, SampleValue.X); CollectedGraphData.Min.Y = FMath::Min(CollectedGraphData.Min.Y, SampleValue.Y); CollectedGraphData.Max.X = FMath::Max(CollectedGraphData.Max.X, SampleValue.X); CollectedGraphData.Max.Y = FMath::Max(CollectedGraphData.Max.Y, SampleValue.Y); const float CurrentTimeStamp = GraphData.TimeStamps[SampleIndex]; if (CurrentTimeStamp < TimeStampWindow.X) { LineData.LeftExtreme = SampleValue; } else if (CurrentTimeStamp > TimeStampWindow.Y) { LineData.RightExtreme = SampleValue; break; } } } } } bDirtyData = false; } if (ULogVisualizerSessionSettings::StaticClass()->GetDefaultObject<ULogVisualizerSessionSettings>()->bEnableGraphsVisualization) { DrawHistogramGraphs(Canvas, NULL); } const TMap<FName, FVisualLogExtensionInterface*>& Extensions = FVisualLogger::Get().GetAllExtensions(); for (const auto& CurrentExtension : Extensions) { CurrentExtension.Value->DrawData(FVisualLoggerEditorInterface::Get(), Canvas); } }
void FVisualLoggerCanvasRenderer::DrawHistogramGraphs(class UCanvas* Canvas, class APlayerController*) { struct FGraphLineData { FName DataName; FVector2D LeftExtreme, RightExtreme; TArray<FVector2D> Samples; }; struct FGraphData { FGraphData() : Min(FVector2D(FLT_MAX, FLT_MAX)), Max(FVector2D(FLT_MIN, FLT_MIN)) {} FVector2D Min, Max; TMap<FName, FGraphLineData> GraphLines; }; if (FLogVisualizer::Get().GetTimeSliderController().IsValid() == false) { return; } TMap<FName, FGraphData> CollectedGraphs; const FVisualLoggerTimeSliderArgs& TimeSliderArgs = FLogVisualizer::Get().GetTimeSliderController()->GetTimeSliderArgs(); TRange<float> LocalViewRange = TimeSliderArgs.ViewRange.Get(); const float LocalViewRangeMin = LocalViewRange.GetLowerBoundValue(); const float LocalViewRangeMax = LocalViewRange.GetUpperBoundValue(); const float LocalSequenceLength = LocalViewRangeMax - LocalViewRangeMin; const float WindowHalfWidth = LocalSequenceLength * TimeSliderArgs.CursorSize.Get() * 0.5f; const FVector2D TimeStampWindow(SelectedEntry.TimeStamp - WindowHalfWidth, SelectedEntry.TimeStamp + WindowHalfWidth); const TArray<FVisualLogDevice::FVisualLogEntryItem> &ObjectItems = CurrentTimeLine.Pin()->GetEntries(); int32 ColorIndex = 0; int32 LeftSideOutsideIndex = INDEX_NONE; int32 RightSideOutsideIndex = INDEX_NONE; for (int32 EntryIndex = 0; EntryIndex < ObjectItems.Num(); ++EntryIndex) { const FVisualLogEntry* CurrentEntry = &(ObjectItems[EntryIndex].Entry); if (CurrentEntry->TimeStamp < TimeStampWindow.X) { LeftSideOutsideIndex = EntryIndex; continue; } if (CurrentEntry->TimeStamp > TimeStampWindow.Y) { RightSideOutsideIndex = EntryIndex; break; } const int32 SamplesNum = CurrentEntry->HistogramSamples.Num(); for (int32 SampleIndex = 0; SampleIndex < SamplesNum; ++SampleIndex) { FVisualLogHistogramSample CurrentSample = CurrentEntry->HistogramSamples[SampleIndex]; const FName CurrentCategory = CurrentSample.Category; const FName CurrentGraphName = CurrentSample.GraphName; const FName CurrentDataName = CurrentSample.DataName; FString GraphFilterName = CurrentSample.GraphName.ToString() +TEXT("$") + CurrentSample.DataName.ToString(); const bool bIsValidByFilter = FCategoryFiltersManager::Get().MatchCategoryFilters(GraphFilterName, ELogVerbosity::All); if (bIsValidByFilter) { FGraphData &GraphData = CollectedGraphs.FindOrAdd(CurrentSample.GraphName); FGraphLineData &LineData = GraphData.GraphLines.FindOrAdd(CurrentSample.DataName); LineData.DataName = CurrentSample.DataName; LineData.Samples.Add(CurrentSample.SampleValue); GraphData.Min.X = FMath::Min(GraphData.Min.X, CurrentSample.SampleValue.X); GraphData.Min.Y = FMath::Min(GraphData.Min.Y, CurrentSample.SampleValue.Y); GraphData.Max.X = FMath::Max(GraphData.Max.X, CurrentSample.SampleValue.X); GraphData.Max.Y = FMath::Max(GraphData.Max.Y, CurrentSample.SampleValue.Y); } } } const int32 ExtremeValueIndexes[] = { LeftSideOutsideIndex != INDEX_NONE ? LeftSideOutsideIndex : 0, RightSideOutsideIndex != INDEX_NONE ? RightSideOutsideIndex : ObjectItems.Num() - 1 }; for (int32 ObjectIndex = 0; ObjectIndex < 2; ++ObjectIndex) { const FVisualLogEntry* CurrentEntry = &(ObjectItems[ExtremeValueIndexes[ObjectIndex]].Entry); const int32 SamplesNum = CurrentEntry->HistogramSamples.Num(); for (int32 SampleIndex = 0; SampleIndex < SamplesNum; ++SampleIndex) { FVisualLogHistogramSample CurrentSample = CurrentEntry->HistogramSamples[SampleIndex]; const FName CurrentCategory = CurrentSample.Category; const FName CurrentGraphName = CurrentSample.GraphName; const FName CurrentDataName = CurrentSample.DataName; FString GraphFilterName = CurrentSample.GraphName.ToString() + TEXT("_") + CurrentSample.DataName.ToString(); const bool bIsValidByFilter = FCategoryFiltersManager::Get().MatchCategoryFilters(GraphFilterName, ELogVerbosity::All); if (bIsValidByFilter) { FGraphData &GraphData = CollectedGraphs.FindOrAdd(CurrentSample.GraphName); FGraphLineData &LineData = GraphData.GraphLines.FindOrAdd(CurrentSample.DataName); LineData.DataName = CurrentSample.DataName; if (ObjectIndex == 0) LineData.LeftExtreme = CurrentSample.SampleValue; else LineData.RightExtreme = CurrentSample.SampleValue; } } } const float GoldenRatioConjugate = 0.618033988749895f; if (CollectedGraphs.Num() > 0) { const FColor GraphsBackgroundColor = ULogVisualizerSettings::StaticClass()->GetDefaultObject<ULogVisualizerSettings>()->GraphsBackgroundColor; const int NumberOfGraphs = CollectedGraphs.Num(); const int32 NumberOfColumns = FMath::CeilToInt(FMath::Sqrt(NumberOfGraphs)); int32 NumberOfRows = FMath::FloorToInt(NumberOfGraphs / NumberOfColumns); if (NumberOfGraphs - NumberOfRows * NumberOfColumns > 0) { NumberOfRows += 1; } const int32 MaxNumberOfGraphs = FMath::Max(NumberOfRows, NumberOfColumns); const float GraphWidth = 0.8f / NumberOfColumns; const float GraphHeight = 0.8f / NumberOfRows; const float XGraphSpacing = 0.2f / (MaxNumberOfGraphs + 1); const float YGraphSpacing = 0.2f / (MaxNumberOfGraphs + 1); const float StartX = XGraphSpacing; float StartY = 0.5 + (0.5 * NumberOfRows - 1) * (GraphHeight + YGraphSpacing); float CurrentX = StartX; float CurrentY = StartY; int32 GraphIndex = 0; int32 CurrentColumn = 0; int32 CurrentRow = 0; bool bDrawExtremesOnGraphs = ULogVisualizerSettings::StaticClass()->GetDefaultObject<ULogVisualizerSettings>()->bDrawExtremesOnGraphs; for (auto It(CollectedGraphs.CreateIterator()); It; ++It) { TWeakObjectPtr<UReporterGraph> HistogramGraph = Canvas->GetReporterGraph(); if (!HistogramGraph.IsValid()) { break; } HistogramGraph->SetNumGraphLines(It->Value.GraphLines.Num()); int32 LineIndex = 0; UFont* Font = GEngine->GetSmallFont(); int32 MaxStringSize = 0; float Hue = 0; auto& CategoriesForGraph = UsedGraphCategories.FindOrAdd(It->Key.ToString()); It->Value.GraphLines.KeySort(TLess<FName>()); for (auto LinesIt(It->Value.GraphLines.CreateConstIterator()); LinesIt; ++LinesIt) { const FString DataName = LinesIt->Value.DataName.ToString(); int32 CategoryIndex = CategoriesForGraph.Find(DataName); if (CategoryIndex == INDEX_NONE) { CategoryIndex = CategoriesForGraph.AddUnique(DataName); } Hue = CategoryIndex * GoldenRatioConjugate; if (Hue > 1) { Hue -= FMath::FloorToFloat(Hue); } HistogramGraph->GetGraphLine(LineIndex)->Color = FLinearColor::FGetHSV(Hue * 255, 0, 244); HistogramGraph->GetGraphLine(LineIndex)->LineName = DataName; HistogramGraph->GetGraphLine(LineIndex)->Data.Append(LinesIt->Value.Samples); HistogramGraph->GetGraphLine(LineIndex)->LeftExtreme = LinesIt->Value.LeftExtreme; HistogramGraph->GetGraphLine(LineIndex)->RightExtreme = LinesIt->Value.RightExtreme; int32 DummyY, StringSizeX; StringSize(Font, StringSizeX, DummyY, *LinesIt->Value.DataName.ToString()); MaxStringSize = StringSizeX > MaxStringSize ? StringSizeX : MaxStringSize; ++LineIndex; } FVector2D GraphSpaceSize; GraphSpaceSize.Y = GraphSpaceSize.X = 0.8f / CollectedGraphs.Num(); HistogramGraph->SetGraphScreenSize(CurrentX, CurrentX + GraphWidth, CurrentY, CurrentY + GraphHeight); CurrentX += GraphWidth + XGraphSpacing; HistogramGraph->SetAxesMinMax(FVector2D(TimeStampWindow.X, It->Value.Min.Y), FVector2D(TimeStampWindow.Y, It->Value.Max.Y)); HistogramGraph->DrawCursorOnGraph(true); HistogramGraph->UseTinyFont(CollectedGraphs.Num() >= 5); HistogramGraph->SetCursorLocation(SelectedEntry.TimeStamp); HistogramGraph->SetNumThresholds(0); HistogramGraph->SetStyles(EGraphAxisStyle::Grid, EGraphDataStyle::Lines); HistogramGraph->SetBackgroundColor(GraphsBackgroundColor); HistogramGraph->SetLegendPosition(/*bShowHistogramLabelsOutside*/ false ? ELegendPosition::Outside : ELegendPosition::Inside); HistogramGraph->OffsetDataSets(/*bOffsetDataSet*/false); HistogramGraph->DrawExtremesOnGraph(bDrawExtremesOnGraphs); HistogramGraph->bVisible = true; HistogramGraph->Draw(Canvas); ++GraphIndex; if (++CurrentColumn >= NumberOfColumns) { CurrentColumn = 0; CurrentRow++; CurrentX = StartX; CurrentY -= GraphHeight + YGraphSpacing; } } } }