// Copyright Epic Games, Inc. All Rights Reserved. #include "Insights/ViewModels/TimingTrackViewport.h" #include "Widgets/Layout/SScrollBar.h" #include //////////////////////////////////////////////////////////////////////////////////////////////////// bool FTimingTrackViewport::UpdateSize(const float InWidth, const float InHeight) { bool bSizeChanged = false; if (Width != InWidth) { Width = InWidth; EndTime = SlateUnitsToTime(Width); AddDirtyFlags(ETimingTrackViewportDirtyFlags::HSizeChanged); bSizeChanged = true; } if (Height != InHeight) { Height = InHeight; AddDirtyFlags(ETimingTrackViewportDirtyFlags::VSizeChanged); bSizeChanged = true; } return bSizeChanged; } //////////////////////////////////////////////////////////////////////////////////////////////////// bool FTimingTrackViewport::ScrollAtTime(const double Time) { const double NewStartTime = AlignTimeToPixel(Time); if (NewStartTime != StartTime) { StartTime = NewStartTime; EndTime = SlateUnitsToTime(Width); AddDirtyFlags(ETimingTrackViewportDirtyFlags::HPositionChanged); return true; } return false; } //////////////////////////////////////////////////////////////////////////////////////////////////// bool FTimingTrackViewport::CenterOnTimeInterval(const double Time, const double Duration) { double NewStartTime = Time; const double ViewportDuration = static_cast(Width) / ScaleX; if (Duration < ViewportDuration) { NewStartTime -= (ViewportDuration - Duration) / 2.0; } return ScrollAtTime(NewStartTime); } //////////////////////////////////////////////////////////////////////////////////////////////////// bool FTimingTrackViewport::ZoomOnTimeInterval(const double Time, const double Duration) { const double NewScaleX = FMath::Clamp(static_cast(Width) / Duration, MinScaleX, MaxScaleX); double NewStartTime = Time; const double NewViewportDuration = static_cast(Width) / NewScaleX; if (Duration < NewViewportDuration) { NewStartTime -= (NewViewportDuration - Duration) / 2; } NewStartTime = AlignTimeToPixel(NewStartTime, NewScaleX); if (NewStartTime != StartTime || NewScaleX != ScaleX) { StartTime = NewStartTime; ScaleX = NewScaleX; EndTime = SlateUnitsToTime(Width); AddDirtyFlags(ETimingTrackViewportDirtyFlags::HPositionChanged | ETimingTrackViewportDirtyFlags::HScaleChanged); return true; } return false; } //////////////////////////////////////////////////////////////////////////////////////////////////// bool FTimingTrackViewport::RelativeZoomWithFixedX(const float Delta, const float X) { constexpr double ZoomStep = 0.25; // as percent double NewScaleX; if (Delta > 0) { NewScaleX = ScaleX * FMath::Pow(1.0 + ZoomStep, static_cast(Delta)); } else { NewScaleX = ScaleX * FMath::Pow(1.0 / (1.0 + ZoomStep), static_cast(-Delta)); } return ZoomWithFixedX(NewScaleX, X); } //////////////////////////////////////////////////////////////////////////////////////////////////// bool FTimingTrackViewport::ZoomWithFixedX(const double NewScaleX, const float X) { const double LocalNewScaleX = FMath::Clamp(NewScaleX, MinScaleX, MaxScaleX); if (LocalNewScaleX != ScaleX) { // Time at local position X should remain the same. So we resolve equation: // StartTime + X / ScaleX == NewStartTime + X / NewScaleX // ==> NewStartTime = StartTime + X / ScaleX - X / NewScaleX // ==> NewStartTime = StartTime + X * (1 / ScaleX - 1 / NewScaleX) StartTime += static_cast(X) * (1.0 / ScaleX - 1.0 / LocalNewScaleX); ScaleX = LocalNewScaleX; EndTime = SlateUnitsToTime(Width); AddDirtyFlags(ETimingTrackViewportDirtyFlags::HPositionChanged | ETimingTrackViewportDirtyFlags::HScaleChanged); return true; } return false; } //////////////////////////////////////////////////////////////////////////////////////////////////// bool FTimingTrackViewport::SetScaleX(const double NewScaleX) { const double LocalNewScaleX = FMath::Clamp(NewScaleX, MinScaleX, MaxScaleX); if (LocalNewScaleX != ScaleX) { ScaleX = LocalNewScaleX; AddDirtyFlags(ETimingTrackViewportDirtyFlags::HScaleChanged); return true; } return false; } //////////////////////////////////////////////////////////////////////////////////////////////////// double FTimingTrackViewport::RestrictEndTime(const double InEndTime) const { if (InEndTime == DBL_MAX || InEndTime == std::numeric_limits::infinity()) { return MaxValidTime; } else { return InEndTime; } } //////////////////////////////////////////////////////////////////////////////////////////////////// double FTimingTrackViewport::RestrictDuration(const double InStartTime, const double InEndTime) const { if (InEndTime == DBL_MAX || InEndTime == std::numeric_limits::infinity()) { return MaxValidTime - InStartTime; } else { return InEndTime - InStartTime; } } //////////////////////////////////////////////////////////////////////////////////////////////////// void FTimingTrackViewport::GetHorizontalScrollLimits(double& OutMinT, double& OutMaxT) { const double ViewportDuration = static_cast(Width) / ScaleX; if (MaxValidTime < ViewportDuration) { OutMinT = MaxValidTime - ViewportDuration; OutMaxT = MinValidTime; } else { OutMinT = MinValidTime - 0.25 * ViewportDuration; OutMaxT = MaxValidTime - 0.75 * ViewportDuration; } } //////////////////////////////////////////////////////////////////////////////////////////////////// bool FTimingTrackViewport::EnforceHorizontalScrollLimits(const double U) { double MinT, MaxT; GetHorizontalScrollLimits(MinT, MaxT); double NewStartTime = StartTime; if (NewStartTime < MinT) { NewStartTime = (1.0 - U) * NewStartTime + U * MinT; if (FMath::IsNearlyEqual(NewStartTime, MinT, 1.0 / ScaleX)) { NewStartTime = MinT; } } else if (NewStartTime > MaxT) { NewStartTime = (1.0 - U) * NewStartTime + U * MaxT; if (FMath::IsNearlyEqual(NewStartTime, MaxT, 1.0 / ScaleX)) { NewStartTime = MaxT; } if (NewStartTime < MinT) { NewStartTime = MinT; } } return ScrollAtTime(NewStartTime); } //////////////////////////////////////////////////////////////////////////////////////////////////// void FTimingTrackViewport::UpdateLayout() { if (Layout.Update()) { AddDirtyFlags(ETimingTrackViewportDirtyFlags::VLayoutChanged); } } //////////////////////////////////////////////////////////////////////////////////////////////////// bool FTimingTrackViewport::OnUserScrolled(TSharedPtr ScrollBar, float ScrollOffset) { const double S = 1.0 / (MaxValidTime - MinValidTime); const double Page = EndTime - StartTime; const float ThumbSizeFraction = FMath::Clamp(static_cast(Page * S), 0.0f, 1.0f); const float OffsetFraction = FMath::Clamp(ScrollOffset, 0.0f, 1.0f - ThumbSizeFraction); ScrollBar->SetState(OffsetFraction, ThumbSizeFraction); const double NewStartTime = MinValidTime + static_cast(OffsetFraction) * (MaxValidTime - MinValidTime); if (NewStartTime != StartTime) { ScrollAtTime(NewStartTime); return true; } return false; } //////////////////////////////////////////////////////////////////////////////////////////////////// void FTimingTrackViewport::UpdateScrollBar(TSharedPtr ScrollBar) const { const double S = 1.0 / (MaxValidTime - MinValidTime); const double Page = EndTime - StartTime; const float ThumbSizeFraction = FMath::Clamp(static_cast(Page * S), 0.0f, 1.0f); const float ScrollOffset = static_cast((StartTime - MinValidTime) * S); const float OffsetFraction = FMath::Clamp(ScrollOffset, 0.0f, 1.0f - ThumbSizeFraction); ScrollBar->SetState(OffsetFraction, ThumbSizeFraction); } //////////////////////////////////////////////////////////////////////////////////////////////////// bool FTimingTrackViewport::OnUserScrolledY(TSharedPtr ScrollBar, float ScrollOffset) { const float S = 1.0f / ScrollHeight; const float Page = Height - TopOffset - BottomOffset; const float ThumbSizeFraction = FMath::Clamp(static_cast(Page * S), 0.0f, 1.0f); const float OffsetFraction = FMath::Clamp(ScrollOffset, 0.0f, 1.0f - ThumbSizeFraction); ScrollBar->SetState(OffsetFraction, ThumbSizeFraction); const float NewScrollPosY = OffsetFraction * ScrollHeight; if (NewScrollPosY != ScrollPosY) { ScrollPosY = NewScrollPosY; AddDirtyFlags(ETimingTrackViewportDirtyFlags::VPositionChanged); return true; } return false; } //////////////////////////////////////////////////////////////////////////////////////////////////// void FTimingTrackViewport::UpdateScrollBarY(TSharedPtr ScrollBar) const { const float S = 1.0f / ScrollHeight; const float Page = Height - TopOffset - BottomOffset; const float ThumbSizeFraction = FMath::Clamp(Page * S, 0.0f, 1.0f); const float ScrollOffset = ScrollPosY * S; const float OffsetFraction = FMath::Clamp(ScrollOffset, 0.0f, 1.0f - ThumbSizeFraction); ScrollBar->SetState(OffsetFraction, ThumbSizeFraction); } ////////////////////////////////////////////////////////////////////////////////////////////////////