// Copyright Epic Games, Inc. All Rights Reserved. #include "SequencerTimeSliderController.h" #include "MVVM/ViewModels/SequencerEditorViewModel.h" #include "MVVM/Selection/Selection.h" #include "MVVM/SharedViewModelData.h" #include "MVVM/Views/SOutlinerView.h" #include "MVVM/Extensions/IClockExtension.h" #include "TrackEditors/TimeWarpTrackEditor.h" #include "Channels/MovieSceneTimeWarpChannel.h" #include "SequencerTimeDomainOverride.h" #include "SequencerToolMenuContext.h" #include "SSequencer.h" #include "Fonts/SlateFontInfo.h" #include "Rendering/DrawElements.h" #include "Misc/Paths.h" #include "Layout/WidgetPath.h" #include "Framework/Application/MenuStack.h" #include "Fonts/FontMeasure.h" #include "Styling/CoreStyle.h" #include "Framework/Application/SlateApplication.h" #include "Widgets/SCompoundWidget.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Textures/SlateIcon.h" #include "Framework/Commands/UIAction.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "Styling/AppStyle.h" #include "SequencerCommonHelpers.h" #include "SequencerSettings.h" #include "Misc/QualifiedFrameTime.h" #include "MovieSceneTimeHelpers.h" #include "CommonFrameRates.h" #include "Sequencer.h" #include "Modules/ModuleManager.h" #include "Decorations/MovieSceneScalingAnchors.h" #include "MovieSceneSequence.h" #include "MovieScene.h" #include "Compilation/MovieSceneCompiledDataManager.h" #include "Evaluation/MovieSceneSequenceHierarchy.h" #include "Misc/NotifyHook.h" #include "IDetailsView.h" #include "PropertyEditorModule.h" #include "ISinglePropertyView.h" #include "IStructureDetailsView.h" #include "FrameNumberDetailsCustomization.h" #define LOCTEXT_NAMESPACE "TimeSlider" namespace ScrubConstants { /** The minimum amount of pixels between each major ticks on the widget */ const int32 MinPixelsPerDisplayTick = 12; /**The smallest number of units between between major tick marks */ const float MinDisplayTickSpacing = 0.001f; /**The fraction of the current view range to scroll per unit delta */ const float ScrollPanFraction = 0.1f; /** Marked frame label box margin */ const int32 MarkLabelBoxMargin = 2; /** Marked frame label box margin on the opposite side of the marker time */ const int32 MarkLabelBoxWideMargin = 4; } bool GSequencerShowTimeWarpScrubberLink = false; FAutoConsoleVariableRef CVarSequencerShowTimeWarpScrubberLink( TEXT("Sequencer.ShowTimeWarpScrubberLink"), GSequencerShowTimeWarpScrubberLink, TEXT("(Default: false) When enabled, shows a dashed link to link warped and unwarped time scrubbers."), ECVF_Default ); FSequencerTimeSliderController::FSequencerTimeSliderController( const FTimeSliderArgs& InArgs, TWeakPtr InWeakSequencer ) : WeakSequencer(InWeakSequencer) , TimeSliderArgs( InArgs ) , DistanceDragged( 0.0f ) , MouseDragType( DRAG_NONE ) , bMouseDownInRegion(false) , bPanning( false ) , HoverMarkIndex( INDEX_NONE ) { ScrubFillBrush = FAppStyle::GetBrush( TEXT( "Sequencer.Timeline.ScrubFill" ) ); FrameBlockScrubHandleDownBrush = FAppStyle::GetBrush( TEXT( "Sequencer.Timeline.FrameBlockScrubHandle" ) ); VanillaScrubHandleDownBrush = FAppStyle::GetBrush( TEXT( "Sequencer.Timeline.ScrubHandle" ) ); FontMeasureService = FSlateApplication::Get().GetRenderer()->GetFontMeasureService().ToSharedPtr(); SmallLayoutFont = FCoreStyle::GetDefaultFontStyle("Regular", 10); SmallBoldLayoutFont = FCoreStyle::GetDefaultFontStyle("Bold", 10); ContextMenuSuppression = 0; TSharedPtr Sequencer = WeakSequencer.Pin(); if (Sequencer.IsValid()) { Sequencer->OnGlobalTimeChanged().AddRaw(this, &FSequencerTimeSliderController::SetIsEvaluating); } } FSequencerTimeSliderController::~FSequencerTimeSliderController() { TSharedPtr Sequencer = WeakSequencer.Pin(); if (Sequencer.IsValid()) { Sequencer->OnGlobalTimeChanged().RemoveAll(this); } } FFrameTime FSequencerTimeSliderController::SnapSequencerTime(FFrameTime InTime) const { using namespace UE::Sequencer; TSharedPtr Sequencer = WeakSequencer.Pin(); if (!Sequencer.IsValid()) { return InTime; } if (Sequencer->GetSequencerSettings()->GetForceWholeFrames()) { if (Sequencer->GetScrubStyle() == ESequencerScrubberStyle::FrameBlock) { // Floor to the display frame InTime = ConvertFrameTime(ConvertFrameTime(InTime, GetTickResolution(), GetDisplayRate()).FloorToFrame(), GetDisplayRate(), GetTickResolution()); } else { // Snap (round) to display rate InTime = FFrameRate::Snap(InTime, GetTickResolution(), GetDisplayRate()); } } if (Sequencer->GetSequencerSettings()->GetIsSnapEnabled()) { TViewModelPtr Clock = Sequencer->GetViewModel()->GetRootSequenceModel().ImplicitCast(); if (Clock.IsValid() && Clock->SupportsSnapping() && Clock->ShouldSnapFrameTime()) { InTime = Clock->SnapFrameTime(InTime); } } return InTime; } FFrameTime FSequencerTimeSliderController::ComputeScrubTimeFromMouse(const FGeometry& Geometry, const FPointerEvent& MouseEvent, FScrubRangeToScreen RangeToScreen) const { FVector2D ScreenSpacePosition = MouseEvent.GetScreenSpacePosition(); FVector2D CursorPos = Geometry.AbsoluteToLocal( ScreenSpacePosition ); double MouseSeconds = RangeToScreen.LocalXToInput( CursorPos.X ); FFrameTime ScrubTime = MouseSeconds * GetTickResolution(); TSharedPtr Sequencer = WeakSequencer.Pin(); if (!Sequencer.IsValid()) { return ScrubTime; } ScrubTime = SnapSequencerTime(ScrubTime); if (Sequencer->GetSequencerSettings()->GetIsSnapEnabled() || MouseEvent.IsShiftDown()) { // SnapTimeToNearestKey will return ScrubTime unmodified if there is no key within range. ScrubTime = SnapTimeToNearestKey(MouseEvent, RangeToScreen, CursorPos.X, ScrubTime); } return ScrubTime; } FFrameTime FSequencerTimeSliderController::ComputeFrameTimeFromMouse(const FGeometry& Geometry, FVector2D ScreenSpacePosition, FScrubRangeToScreen RangeToScreen, bool CheckSnapping) const { FVector2D CursorPos = Geometry.AbsoluteToLocal( ScreenSpacePosition ); double MouseValue = RangeToScreen.LocalXToInput( CursorPos.X ); TSharedPtr Sequencer = WeakSequencer.Pin(); if (!Sequencer.IsValid()) { return MouseValue * GetTickResolution(); } if (CheckSnapping && Sequencer->GetSequencerSettings()->GetForceWholeFrames()) { FFrameNumber SnappedFrameNumber = (MouseValue * GetDisplayRate()).FloorToFrame(); FQualifiedFrameTime RoundedPlayFrame = FQualifiedFrameTime(SnappedFrameNumber, GetDisplayRate()); return RoundedPlayFrame.ConvertTo(GetTickResolution()); } else { return MouseValue * GetTickResolution(); } } FSequencerTimeSliderController::FScrubberMetrics FSequencerTimeSliderController::GetHitTestScrubPixelMetrics(const FScrubRangeToScreen& RangeToScreen) const { static const float DragToleranceSlateUnits = 2.f, MouseTolerance = 2.f; return GetScrubPixelMetrics(FQualifiedFrameTime(TimeSliderArgs.ScrubPosition.Get(), GetTickResolution()), RangeToScreen, DragToleranceSlateUnits + MouseTolerance); } FSequencerTimeSliderController::FScrubberMetrics FSequencerTimeSliderController::GetScrubPixelMetrics(const FQualifiedFrameTime& ScrubTime, const FScrubRangeToScreen& RangeToScreen, float DilationPixels) const { FFrameRate DisplayRate = GetDisplayRate(); FScrubberMetrics Metrics; static float MinScrubSize = 14.f; const FFrameNumber Frame = ScrubTime.ConvertTo(DisplayRate).FloorToFrame(); float FrameStartPixel = RangeToScreen.InputToLocalX( Frame / DisplayRate ); float FrameEndPixel = RangeToScreen.InputToLocalX( (Frame+1) / DisplayRate ) - 1; { float RoundedStartPixel = FMath::RoundToInt(FrameStartPixel); FrameEndPixel -= (FrameStartPixel - RoundedStartPixel); FrameStartPixel = RoundedStartPixel; FrameEndPixel = FMath::Max(FrameEndPixel, FrameStartPixel + 1); } // Store off the pixel width of the frame Metrics.FrameExtentsPx = TRange(FrameStartPixel - DilationPixels, FrameEndPixel + DilationPixels); // Set the style of the scrub handle TSharedPtr Sequencer = WeakSequencer.Pin(); Metrics.Style = Sequencer.IsValid() ? Sequencer->GetScrubStyle() : ESequencerScrubberStyle::Vanilla; // Always draw the extents on the section area for frame block styles Metrics.bDrawExtents = Metrics.Style == ESequencerScrubberStyle::FrameBlock; // If it's vanilla style or too small to show the frame width, set that up if (Metrics.Style == ESequencerScrubberStyle::Vanilla || FrameEndPixel - FrameStartPixel < MinScrubSize) { Metrics.Style = ESequencerScrubberStyle::Vanilla; float ScrubPixel = RangeToScreen.InputToLocalX(ScrubTime.AsSeconds()); Metrics.HandleRangePx = TRange(ScrubPixel - MinScrubSize*.5f - DilationPixels, ScrubPixel + MinScrubSize*.5f + DilationPixels); } else { Metrics.HandleRangePx = Metrics.FrameExtentsPx; } return Metrics; } void FSequencerTimeSliderController::DrawTicks( FSlateWindowElementList& OutDrawElements, const TRange& ViewRange, const FScrubRangeToScreen& RangeToScreen, FDrawTickArgs& InArgs ) const { using namespace UE::Sequencer; TSharedPtr Sequencer = WeakSequencer.Pin(); if (!Sequencer.IsValid()) { return; } TViewModelPtr Clock = Sequencer->GetViewModel()->GetRootSequenceModel().ImplicitCast(); if (Clock && Clock->DrawTicks(Sequencer, OutDrawElements, ViewRange, RangeToScreen, InArgs)) { return; } DrawLinearTicks(OutDrawElements, ViewRange, RangeToScreen, InArgs); } void FSequencerTimeSliderController::DrawLinearTicks(FSlateWindowElementList& OutDrawElements, const TRange& ViewRange, const FScrubRangeToScreen& RangeToScreen, FDrawTickArgs& InArgs) const { TSharedPtr Sequencer = WeakSequencer.Pin(); if (!Sequencer.IsValid()) { return; } FFrameRate TickResolution = GetTickResolution(); FFrameRate DisplayRate = GetDisplayRate(); FPaintGeometry PaintGeometry = InArgs.AllottedGeometry.ToPaintGeometry(); FSlateFontInfo TickFrameFont = FCoreStyle::GetDefaultFontStyle("Regular", 8); double MajorGridStep = 0.0; int32 MinorDivisions = 0; if (!Sequencer->GetGridMetrics(InArgs.AllottedGeometry.Size.X, ViewRange.GetLowerBoundValue(), ViewRange.GetUpperBoundValue(), MajorGridStep, MinorDivisions)) { return; } if (InArgs.bOnlyDrawMajorTicks) { MinorDivisions = 0; } TArray LinePoints; LinePoints.SetNumUninitialized(2); const bool bAntiAliasLines = false; const double FirstMajorLine = FMath::FloorToDouble(ViewRange.GetLowerBoundValue() / MajorGridStep) * MajorGridStep; const double LastMajorLine = FMath::CeilToDouble(ViewRange.GetUpperBoundValue() / MajorGridStep) * MajorGridStep; const float FlooredScrubPx = RangeToScreen.InputToLocalX(ConvertFrameTime(TimeSliderArgs.ScrubPosition.Get(), TickResolution, GetDisplayRate()).FloorToFrame() / DisplayRate); TSharedPtr> NumericTypeInterface = Sequencer->GetNumericTypeInterface(); for (double CurrentMajorLine = FirstMajorLine; CurrentMajorLine < LastMajorLine; CurrentMajorLine += MajorGridStep) { float MajorLinePx = RangeToScreen.InputToLocalX( CurrentMajorLine ); LinePoints[0] = FVector2D( MajorLinePx, InArgs.TickOffset ); LinePoints[1] = FVector2D( MajorLinePx, InArgs.TickOffset + InArgs.MajorTickHeight ); // Draw each tick mark FSlateDrawElement::MakeLines( OutDrawElements, InArgs.StartLayer, PaintGeometry, LinePoints, InArgs.DrawEffects, InArgs.TickColor, bAntiAliasLines ); if (!InArgs.bOnlyDrawMajorTicks && !FMath::IsNearlyEqual(MajorLinePx, FlooredScrubPx, 3.f)) { FString FrameString = NumericTypeInterface->ToString((CurrentMajorLine * TickResolution).RoundToFrame().Value); // Space the text between the tick mark but slightly above FVector2D TextOffset( MajorLinePx + 5.f, InArgs.bMirrorLabels ? 1.f : FMath::Abs( InArgs.AllottedGeometry.Size.Y - (InArgs.MajorTickHeight+3.f) ) ); FSlateDrawElement::MakeText( OutDrawElements, InArgs.StartLayer+1, InArgs.AllottedGeometry.ToPaintGeometry( InArgs.AllottedGeometry.Size, FSlateLayoutTransform(TextOffset) ), FrameString, TickFrameFont, InArgs.DrawEffects, InArgs.TickColor*0.65f ); } for (int32 Step = 1; Step < MinorDivisions; ++Step) { // Compute the size of each tick mark. If we are half way between to visible values display a slightly larger tick mark const float MinorTickHeight = ( (MinorDivisions % 2 == 0) && (Step % (MinorDivisions/2)) == 0 ) ? 6.0f : 2.0f; const float MinorLinePx = RangeToScreen.InputToLocalX( CurrentMajorLine + Step*MajorGridStep/MinorDivisions ); LinePoints[0] = FVector2D(MinorLinePx, InArgs.bMirrorLabels ? 0.0f : FMath::Abs( InArgs.AllottedGeometry.Size.Y - MinorTickHeight ) ); LinePoints[1] = FVector2D(MinorLinePx, LinePoints[0].Y + MinorTickHeight); // Draw each sub mark FSlateDrawElement::MakeLines( OutDrawElements, InArgs.StartLayer, PaintGeometry, LinePoints, InArgs.DrawEffects, InArgs.TickColor, bAntiAliasLines ); } } } int32 FSequencerTimeSliderController::DrawScalingAnchors(const FGeometry& AllottedGeometry, const FScrubRangeToScreen& RangeToScreen, FSlateWindowElementList& OutDrawElements, int32 LayerId, const ESlateDrawEffect& DrawEffects) const { UMovieSceneScalingAnchors* Anchors = WeakSequencer.Pin()->GetFocusedMovieSceneSequence()->GetMovieScene()->FindDecoration(); if (!Anchors || !WeakSequencer.Pin()->GetSequencerSettings()->GetShowScalingAnchors()) { return LayerId; } TArray LinePoints; for (const TPair& Pair : Anchors->GetCurrentAnchors()) { const double StartSeconds = Pair.Value.Position / GetTickResolution(); const float StartLinePos = RangeToScreen.InputToLocalX(StartSeconds); LinePoints.Reset(2); LinePoints.Emplace(FVector2f(StartLinePos, 0.0f)); LinePoints.Emplace(FVector2f(StartLinePos, FMath::FloorToFloat(AllottedGeometry.Size.Y))); FSlateDrawElement::MakeDashedLines( OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), MoveTemp(LinePoints), DrawEffects, FLinearColor::Yellow.CopyWithNewOpacity(0.25f) ); if (Pair.Value.Duration > 0) { const double EndSeconds = (Pair.Value.Position + Pair.Value.Duration) / GetTickResolution(); const float EndLinePos = RangeToScreen.InputToLocalX(EndSeconds); LinePoints.Reset(2); LinePoints.Emplace(FVector2f(EndLinePos, 0.0f)); LinePoints.Emplace(FVector2f(EndLinePos, FMath::FloorToFloat(AllottedGeometry.Size.Y))); FSlateDrawElement::MakeDashedLines( OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), MoveTemp(LinePoints), DrawEffects, FLinearColor::Yellow.CopyWithNewOpacity(0.25f) ); } } return LayerId + 1; } int32 FSequencerTimeSliderController::DrawMarkedFrames( const FGeometry& AllottedGeometry, const FScrubRangeToScreen& RangeToScreen, FSlateWindowElementList& OutDrawElements, int32 LayerId, const ESlateDrawEffect& DrawEffects, const FWidgetStyle& InWidgetStyle, bool bDrawLabels ) const { if (!WeakSequencer.Pin()->GetSequencerSettings()->GetShowMarkedFrames()) { return LayerId; } using namespace UE::Sequencer; const TArray & MarkedFrames = TimeSliderArgs.MarkedFrames.Get(); const TArray & GlobalMarkedFrames = TimeSliderArgs.GlobalMarkedFrames.Get(); if (MarkedFrames.Num() < 1 && GlobalMarkedFrames.Num() < 1) { return LayerId; } const FMarkedFrameSelection& SelectedMarkedFrames = WeakSequencer.Pin()->GetViewModel()->GetSelection()->MarkedFrames; //const FLinearColor SelectedColor = FLinearColor::White; //FAppStyle::GetSlateColor("SelectionColor").GetColor(InWidgetStyle); const FLinearColor WhiteColorHSV = FLinearColor::White.LinearRGBToHSV(); const FLinearColor DefaultMarkedFrameColor = WeakSequencer.Pin()->GetSequencerSettings()->GetMarkedFrameColor(); auto DrawFrameMarkers = ([=, this](const TArray & InMarkedFrames, FSlateWindowElementList& DrawElements, bool bIsGlobal) { for (int32 MarkIndex = 0; MarkIndex < InMarkedFrames.Num(); ++MarkIndex) { const FMovieSceneMarkedFrame& MarkedFrame(InMarkedFrames[MarkIndex]); double Seconds = MarkedFrame.FrameNumber / GetTickResolution(); const bool bIsHovered = (!bIsGlobal && HoverMarkIndex == MarkIndex); const bool bIsSelected = (!bIsGlobal && SelectedMarkedFrames.IsSelected(MarkIndex)); // Get a selected color that's the marked frame color but at full opacity, full brightness, and a bit desaturated if it's // already bright to begin with. const FLinearColor MarkedFrameColor = MarkedFrame.bUseCustomColor ? MarkedFrame.CustomColor : DefaultMarkedFrameColor; const FLinearColor MarkedFrameColorHSV = MarkedFrameColor.CopyWithNewOpacity(1.f).LinearRGBToHSV(); const FLinearColor SelectedColor = FLinearColor::LerpUsingHSV( MarkedFrameColorHSV, FLinearColor( MarkedFrameColorHSV.R, MarkedFrameColorHSV.G * (1.f - MarkedFrameColorHSV.B), 1.f), 0.5f) .HSVToLinearRGB(); FLinearColor DrawColor = bIsGlobal ? MarkedFrameColor.Desaturate(0.25f) : (bIsSelected ? SelectedColor : MarkedFrameColor); const float LinePos = RangeToScreen.InputToLocalX(Seconds); TArray LinePoints; LinePoints.AddUninitialized(2); LinePoints[0] = FVector2D(LinePos, 0.0f); LinePoints[1] = FVector2D(LinePos, FMath::FloorToFloat(AllottedGeometry.Size.Y)); FSlateDrawElement::MakeLines( DrawElements, LayerId + 1, AllottedGeometry.ToPaintGeometry(), LinePoints, DrawEffects, DrawColor, false, (bIsHovered || bIsSelected) ? 2.f : 1.f ); FString LabelString = MarkedFrame.Label; if (bDrawLabels && !LabelString.IsEmpty()) { // Draw the label next to the marked frame line bool bDrawLeft; FVector2D TextPosition, TextSize; GetMarkLabelGeometry(AllottedGeometry, RangeToScreen, MarkedFrame, TextPosition, TextSize, bDrawLeft); const FSlateBrush* LabelBrush = bDrawLeft ? FAppStyle::GetBrush("Sequencer.MarkedFrame.LabelLeft") : FAppStyle::GetBrush("Sequencer.MarkedFrame.LabelRight"); if (bIsHovered || bIsSelected) { FSlateDrawElement::MakeBox( DrawElements, LayerId + 1, AllottedGeometry.ToPaintGeometry( TextSize + ScrubConstants::MarkLabelBoxMargin + ScrubConstants::MarkLabelBoxWideMargin, FSlateLayoutTransform(FVector2D(LinePos, 0.f))), LabelBrush, DrawEffects, DrawColor.CopyWithNewOpacity(0.3f) ); } FSlateDrawElement::MakeText( DrawElements, LayerId + 1, AllottedGeometry.ToPaintGeometry(TextSize, FSlateLayoutTransform(TextPosition)), LabelString, (bIsHovered || bIsSelected) ? SmallBoldLayoutFont : SmallLayoutFont, DrawEffects, DrawColor ); } } }); DrawFrameMarkers(GlobalMarkedFrames, OutDrawElements, true); DrawFrameMarkers(MarkedFrames, OutDrawElements, false); return LayerId + 1; } int32 FSequencerTimeSliderController::DrawVerticalFrames(const FGeometry& AllottedGeometry, const FScrubRangeToScreen& RangeToScreen, FSlateWindowElementList& OutDrawElements, int32 LayerId, const ESlateDrawEffect& DrawEffects) const { TSet VerticalFrames = TimeSliderArgs.VerticalFrames.Get(); if (VerticalFrames.Num() < 1) { return LayerId; } for (FFrameNumber TickFrame : VerticalFrames) { double Seconds = TickFrame / GetTickResolution(); const float LinePos = RangeToScreen.InputToLocalX(Seconds); TArray LinePoints; LinePoints.AddUninitialized(2); LinePoints[0] = FVector2D(LinePos, 0.0f); LinePoints[1] = FVector2D(LinePos, FMath::FloorToFloat(AllottedGeometry.Size.Y)); FSlateDrawElement::MakeLines( OutDrawElements, LayerId + 1, AllottedGeometry.ToPaintGeometry(), LinePoints, DrawEffects, FLinearColor(0.7f, 0.7f, 0.f, 0.4f), false ); } return LayerId + 1; } int32 FSequencerTimeSliderController::OnPaintTimeSlider( bool bMirrorLabels, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const { using namespace UE::Sequencer; TSharedPtr Sequencer = WeakSequencer.Pin(); if (!Sequencer.IsValid()) { return LayerId; } const bool bEnabled = bParentEnabled; const ESlateDrawEffect DrawEffects = bEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect; TRange LocalViewRange = GetViewRange(); 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 ); // draw tick marks float MajorTickHeight = 9.0f; TViewModelPtr Clock = Sequencer->GetViewModel()->GetRootSequenceModel().ImplicitCast(); if (Clock) { MajorTickHeight = Clock->GetCustomMajorTickSize(Sequencer); } FDrawTickArgs Args; { Args.AllottedGeometry = AllottedGeometry; Args.bMirrorLabels = bMirrorLabels; Args.bOnlyDrawMajorTicks = false; Args.TickColor = FLinearColor::White; Args.CullingRect = MyCullingRect; Args.DrawEffects = DrawEffects; Args.StartLayer = LayerId; Args.TickOffset = bMirrorLabels ? 0.0f : FMath::Abs( AllottedGeometry.Size.Y - MajorTickHeight ); Args.MajorTickHeight = MajorTickHeight; } DrawTicks( OutDrawElements, LocalViewRange, RangeToScreen, Args ); // draw playback & selection range FPaintPlaybackRangeArgs PlaybackRangeArgs( bMirrorLabels ? FAppStyle::GetBrush("Sequencer.Timeline.PlayRange_Bottom_L") : FAppStyle::GetBrush("Sequencer.Timeline.PlayRange_Top_L"), bMirrorLabels ? FAppStyle::GetBrush("Sequencer.Timeline.PlayRange_Bottom_R") : FAppStyle::GetBrush("Sequencer.Timeline.PlayRange_Top_R"), 6.f ); LayerId = DrawPlaybackRange(AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, RangeToScreen, PlaybackRangeArgs); LayerId = DrawSubSequenceRange(AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, RangeToScreen, PlaybackRangeArgs); PlaybackRangeArgs.SolidFillOpacity = 0.05f; LayerId = DrawSelectionRange(AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, RangeToScreen, PlaybackRangeArgs); // Draw the scrub handle FQualifiedFrameTime ScrubPosition = FQualifiedFrameTime(TimeSliderArgs.ScrubPosition.Get(), GetTickResolution()); FScrubberMetrics ScrubMetrics = GetScrubPixelMetrics(ScrubPosition, RangeToScreen); const float HandleStart = ScrubMetrics.HandleRangePx.GetLowerBoundValue(); const float HandleEnd = ScrubMetrics.HandleRangePx.GetUpperBoundValue(); const int32 ArrowLayer = LayerId + 2; FPaintGeometry MyGeometry = AllottedGeometry.ToPaintGeometry( FVector2f( HandleEnd - HandleStart, AllottedGeometry.Size.Y-1.f ), FSlateLayoutTransform(FVector2f( HandleStart, 1.f )) ); FTimeWarpTrackExtension* TimeWarpExtension = Sequencer->GetViewModel()->GetRootSequenceModel()->GetSharedData()->CastDynamic(); const FTimeWarpTrackModel* ActiveTimeWarpTrack = TimeWarpExtension ? TimeWarpExtension->GetActiveTimeWarpTrack() : nullptr; const bool bIsWarped = (Sequencer->GetSequencerSettings()->GetTimeWarpDisplayMode() == ESequencerTimeWarpDisplay::WarpedTime && ActiveTimeWarpTrack != nullptr); FLinearColor ScrubColor = bIsWarped ? FStyleColors::AccentOrange.GetSpecifiedColor() : FStyleColors::AccentRed.GetSpecifiedColor(); if (!bIsEvaluating) { ScrubColor = FStyleColors::AccentYellow.GetSpecifiedColor().CopyWithNewOpacity(.7f); } const FSlateBrush* Brush = ScrubMetrics.Style == ESequencerScrubberStyle::Vanilla ? VanillaScrubHandleDownBrush : FrameBlockScrubHandleDownBrush; if (bMirrorLabels) { FSlateRenderTransform FlipTransform(FScale2f(1.f, -1.f), FVector2f(0.f, AllottedGeometry.Size.Y)); MyGeometry.SetRenderTransform( Concatenate( FlipTransform, MyGeometry.GetAccumulatedRenderTransform() ) ); } FSlateDrawElement::MakeBox( OutDrawElements, ArrowLayer, MyGeometry, Brush, DrawEffects, ScrubColor ); LayerId = DrawMarkedFrames(AllottedGeometry, RangeToScreen, OutDrawElements, LayerId, DrawEffects, InWidgetStyle, true); { // Draw the current time next to the scrub handle FString FrameString; FLinearColor TextColor = Args.TickColor; if (TimeSliderArgs.ScrubPositionText.IsSet()) { FrameString = TimeSliderArgs.ScrubPositionText.Get(); } else { FrameString = Sequencer->GetNumericTypeInterface()->ToString(ScrubPosition.Time.GetFrame().Value); } if (TimeSliderArgs.ScrubPositionParent.Get() != MovieSceneSequenceID::Invalid) { TextColor = FLinearColor::Yellow; } FVector2D TextSize = FontMeasureService->Measure(FrameString, SmallLayoutFont); // Flip the text position if getting near the end of the view range static const float TextOffsetPx = 2.f; bool bDrawLeft = (AllottedGeometry.Size.X - HandleEnd) < (TextSize.X + 14.f) - TextOffsetPx; float TextPosition = bDrawLeft ? HandleStart - TextSize.X - TextOffsetPx : HandleEnd + TextOffsetPx; FVector2D TextOffset( TextPosition, Args.bMirrorLabels ? Args.AllottedGeometry.Size.Y - TextSize.Y : 0.f ); FSlateDrawElement::MakeText( OutDrawElements, Args.StartLayer+1, Args.AllottedGeometry.ToPaintGeometry( TextSize, FSlateLayoutTransform(TextOffset) ), FrameString, SmallLayoutFont, Args.DrawEffects, TextColor ); } if (MouseDragType == DRAG_SETTING_RANGE && MouseDownPosition[0].IsSet() && MouseDownPosition[1].IsSet()) { FFrameRate Resolution = GetTickResolution(); FFrameTime MouseDownTime[2]; FScrubRangeToScreen MouseDownRange(GetViewRange(), MouseDownGeometry.Size); MouseDownTime[0] = ComputeFrameTimeFromMouse(MouseDownGeometry, MouseDownPosition[0].GetValue(), MouseDownRange); MouseDownTime[1] = ComputeFrameTimeFromMouse(MouseDownGeometry, MouseDownPosition[1].GetValue(), MouseDownRange); float MouseStartPosX = RangeToScreen.InputToLocalX(MouseDownTime[0] / Resolution); float MouseEndPosX = RangeToScreen.InputToLocalX(MouseDownTime[1] / Resolution); float RangePosX = MouseStartPosX < MouseEndPosX ? MouseStartPosX : MouseEndPosX; float RangeSizeX = FMath::Abs(MouseStartPosX - MouseEndPosX); FPaintGeometry PaintGeom = AllottedGeometry.ToPaintGeometry( FVector2f(RangeSizeX, AllottedGeometry.Size.Y), FSlateLayoutTransform(FVector2f(RangePosX, 0.f)) ); if (bMirrorLabels) { FSlateRenderTransform FlipTransform(FScale2f(1.f, -1.f), FVector2f(0.f, AllottedGeometry.Size.Y)); PaintGeom.SetRenderTransform( Concatenate( FlipTransform, PaintGeom.GetAccumulatedRenderTransform() ) ); } FSlateDrawElement::MakeBox( OutDrawElements, LayerId+1, PaintGeom, VanillaScrubHandleDownBrush, DrawEffects, MouseStartPosX < MouseEndPosX ? FLinearColor(0.5f, 0.5f, 0.5f) : FLinearColor(0.25f, 0.3f, 0.3f) ); } return ArrowLayer; } return LayerId; } int32 FSequencerTimeSliderController::DrawSelectionRange(const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FScrubRangeToScreen& RangeToScreen, const FPaintPlaybackRangeArgs& Args) const { TSharedPtr Sequencer = WeakSequencer.Pin(); if (!Sequencer.IsValid()) { return LayerId; } TRange SelectionRange = TimeSliderArgs.SelectionRange.Get() / GetTickResolution(); if (!SelectionRange.IsEmpty()) { const float SelectionRangeL = RangeToScreen.InputToLocalX(SelectionRange.GetLowerBoundValue()); const float SelectionRangeR = RangeToScreen.InputToLocalX(SelectionRange.GetUpperBoundValue()) - 1; const auto DrawColor = FAppStyle::GetSlateColor("SelectionColor").GetColor(FWidgetStyle()); if (Args.SolidFillOpacity > 0.f) { FSlateDrawElement::MakeBox( OutDrawElements, LayerId + 1, AllottedGeometry.ToPaintGeometry(FVector2f(SelectionRangeR - SelectionRangeL, AllottedGeometry.Size.Y), FSlateLayoutTransform(FVector2f(SelectionRangeL, 0.f))), FAppStyle::GetBrush("WhiteBrush"), ESlateDrawEffect::None, DrawColor.CopyWithNewOpacity(Args.SolidFillOpacity) ); } FSlateDrawElement::MakeBox( OutDrawElements, LayerId + 1, AllottedGeometry.ToPaintGeometry(FVector2f(Args.BrushWidth, AllottedGeometry.Size.Y), FSlateLayoutTransform(FVector2f(SelectionRangeL, 0.f))), Args.StartBrush, ESlateDrawEffect::None, DrawColor ); FSlateDrawElement::MakeBox( OutDrawElements, LayerId + 1, AllottedGeometry.ToPaintGeometry(FVector2f(Args.BrushWidth, AllottedGeometry.Size.Y), FSlateLayoutTransform(FVector2f(SelectionRangeR - Args.BrushWidth, 0.f))), Args.EndBrush, ESlateDrawEffect::None, DrawColor ); } return LayerId + 1; } int32 FSequencerTimeSliderController::DrawPlaybackRange(const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FScrubRangeToScreen& RangeToScreen, const FPaintPlaybackRangeArgs& Args) const { TSharedPtr Sequencer = WeakSequencer.Pin(); if (!Sequencer.IsValid()) { return LayerId; } if (!TimeSliderArgs.PlaybackRange.IsSet()) { return LayerId; } const uint8 OpacityBlend = TimeSliderArgs.SubSequenceRange.Get().IsSet() ? 128 : 255; TRange PlaybackRange = TimeSliderArgs.PlaybackRange.Get(); FFrameRate TickResolution = GetTickResolution(); const float PlaybackRangeL = RangeToScreen.InputToLocalX(PlaybackRange.GetLowerBoundValue() / TickResolution); const float PlaybackRangeR = RangeToScreen.InputToLocalX(PlaybackRange.GetUpperBoundValue() / TickResolution) - 1; const FLinearColor PlaybackRangeStartColor = Sequencer->GetSequencerSettings()->GetPlaybackRangeStartColor(); const FLinearColor PlaybackRangeEndColor = Sequencer->GetSequencerSettings()->GetPlaybackRangeEndColor(); FSlateDrawElement::MakeBox( OutDrawElements, LayerId+1, AllottedGeometry.ToPaintGeometry(FVector2f(Args.BrushWidth, AllottedGeometry.Size.Y), FSlateLayoutTransform(FVector2f(PlaybackRangeL, 0.f))), Args.StartBrush, ESlateDrawEffect::None, PlaybackRangeStartColor.CopyWithNewOpacity(OpacityBlend) ); FSlateDrawElement::MakeBox( OutDrawElements, LayerId+1, AllottedGeometry.ToPaintGeometry(FVector2f(Args.BrushWidth, AllottedGeometry.Size.Y), FSlateLayoutTransform(FVector2f(PlaybackRangeR - Args.BrushWidth, 0.f))), Args.EndBrush, ESlateDrawEffect::None, PlaybackRangeEndColor.CopyWithNewOpacity(OpacityBlend) ); // Black tint for excluded regions FSlateDrawElement::MakeBox( OutDrawElements, LayerId+1, AllottedGeometry.ToPaintGeometry(FVector2f(PlaybackRangeL, AllottedGeometry.Size.Y), FSlateLayoutTransform()), FAppStyle::GetBrush("WhiteBrush"), ESlateDrawEffect::None, FLinearColor::Black.CopyWithNewOpacity(0.3f * OpacityBlend / 255.f) ); FSlateDrawElement::MakeBox( OutDrawElements, LayerId+1, AllottedGeometry.ToPaintGeometry(FVector2f(AllottedGeometry.Size.X - PlaybackRangeR, AllottedGeometry.Size.Y), FSlateLayoutTransform(FVector2f(PlaybackRangeR, 0.f))), FAppStyle::GetBrush("WhiteBrush"), ESlateDrawEffect::None, FLinearColor::Black.CopyWithNewOpacity(0.3f * OpacityBlend / 255.f) ); return LayerId + 1; } int32 FSequencerTimeSliderController::DrawSubSequenceRange(const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FScrubRangeToScreen& RangeToScreen, const FPaintPlaybackRangeArgs& Args) const { TSharedPtr Sequencer = WeakSequencer.Pin(); if (!Sequencer.IsValid()) { return LayerId; } TOptional> RangeValue; RangeValue = TimeSliderArgs.SubSequenceRange.Get(RangeValue); if (!RangeValue.IsSet() || RangeValue->IsEmpty()) { return LayerId; } const FFrameRate Resolution = GetTickResolution(); const FFrameNumber LowerFrame = RangeValue.GetValue().GetLowerBoundValue(); const FFrameNumber UpperFrame = RangeValue.GetValue().GetUpperBoundValue(); const float SubSequenceRangeL = RangeToScreen.InputToLocalX(LowerFrame / Resolution) - 1; const float SubSequenceRangeR = RangeToScreen.InputToLocalX(UpperFrame / Resolution) + 1; static const FSlateBrush* LineBrushL(FAppStyle::GetBrush("Sequencer.Timeline.PlayRange_L")); static const FSlateBrush* LineBrushR(FAppStyle::GetBrush("Sequencer.Timeline.PlayRange_R")); const FLinearColor PlaybackRangeStartColor = Sequencer->GetSequencerSettings()->GetPlaybackRangeStartColor(); const FLinearColor PlaybackRangeEndColor = Sequencer->GetSequencerSettings()->GetPlaybackRangeEndColor(); FColor GreenTint(32, 128, 32); // 120, 75, 50 (HSV) FSlateDrawElement::MakeBox( OutDrawElements, LayerId+1, AllottedGeometry.ToPaintGeometry(FVector2f(Args.BrushWidth, AllottedGeometry.Size.Y), FSlateLayoutTransform(FVector2f(SubSequenceRangeL, 0.f))), LineBrushL, ESlateDrawEffect::None, PlaybackRangeStartColor ); FColor RedTint(192, 48, 48); // 0, 75, 75 (HSV) FSlateDrawElement::MakeBox( OutDrawElements, LayerId+1, AllottedGeometry.ToPaintGeometry(FVector2f(Args.BrushWidth, AllottedGeometry.Size.Y), FSlateLayoutTransform(FVector2f(SubSequenceRangeR - Args.BrushWidth, 0.f))), LineBrushR, ESlateDrawEffect::None, PlaybackRangeEndColor ); // Black tint for excluded regions FSlateDrawElement::MakeBox( OutDrawElements, LayerId+1, AllottedGeometry.ToPaintGeometry(FVector2f(SubSequenceRangeL, AllottedGeometry.Size.Y), FSlateLayoutTransform()), FAppStyle::GetBrush("WhiteBrush"), ESlateDrawEffect::None, FLinearColor::Black.CopyWithNewOpacity(0.3f) ); FSlateDrawElement::MakeBox( OutDrawElements, LayerId+1, AllottedGeometry.ToPaintGeometry(FVector2f(AllottedGeometry.Size.X - SubSequenceRangeR, AllottedGeometry.Size.Y), FSlateLayoutTransform(FVector2f(SubSequenceRangeR, 0.f))), FAppStyle::GetBrush("WhiteBrush"), ESlateDrawEffect::None, FLinearColor::Black.CopyWithNewOpacity(0.3f) ); // Hash applied to the left and right of the sequence bounds FSlateDrawElement::MakeBox( OutDrawElements, LayerId+1, AllottedGeometry.ToPaintGeometry(FVector2f(16.f, AllottedGeometry.Size.Y), FSlateLayoutTransform(FVector2f(SubSequenceRangeL - 16.f, 0.f))), FAppStyle::GetBrush("Sequencer.Timeline.SubSequenceRangeHashL"), ESlateDrawEffect::None, PlaybackRangeStartColor ); FSlateDrawElement::MakeBox( OutDrawElements, LayerId+1, AllottedGeometry.ToPaintGeometry(FVector2f(16.f, AllottedGeometry.Size.Y), FSlateLayoutTransform(FVector2f(SubSequenceRangeR, 0.f))), FAppStyle::GetBrush("Sequencer.Timeline.SubSequenceRangeHashR"), ESlateDrawEffect::None, PlaybackRangeEndColor ); return LayerId + 1; } FReply FSequencerTimeSliderController::OnMouseButtonDown( SWidget& WidgetOwner, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) { MouseDragType = DRAG_NONE; DistanceDragged = 0; MouseDownPlaybackRange = TimeSliderArgs.PlaybackRange.Get(); MouseDownSelectionRange = TimeSliderArgs.SelectionRange.Get(); MouseDownPosition[0] = MouseDownPosition[1] = MouseEvent.GetScreenSpacePosition(); MouseDownGeometry = MyGeometry; bMouseDownInRegion = false; FVector2D CursorPos = MouseEvent.GetScreenSpacePosition(); FVector2D LocalPos = MouseDownGeometry.AbsoluteToLocal(CursorPos); if (LocalPos.Y >= 0 && LocalPos.Y < MouseDownGeometry.GetLocalSize().Y) { bMouseDownInRegion = true; } return FReply::Unhandled(); } FReply FSequencerTimeSliderController::OnMouseButtonUp( SWidget& WidgetOwner, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) { bool bHandleLeftMouseButton = MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton && WidgetOwner.HasMouseCapture(); bool bHandleRightMouseButton = MouseEvent.GetEffectingButton() == EKeys::RightMouseButton && WidgetOwner.HasMouseCapture() && TimeSliderArgs.AllowZoom ; bool bHandleMiddleMouseButton = MouseEvent.GetEffectingButton() == EKeys::MiddleMouseButton && WidgetOwner.HasMouseCapture(); FScrubRangeToScreen RangeToScreen = FScrubRangeToScreen(GetViewRange(), MyGeometry.Size); FFrameTime MouseTime = ComputeFrameTimeFromMouse(MyGeometry, MouseEvent.GetScreenSpacePosition(), RangeToScreen); if ( bHandleRightMouseButton ) { if (!bPanning) { // Open a context menu if allowed if (ContextMenuSuppression == 0 && TimeSliderArgs.PlaybackRange.IsSet()) { TSharedRef MenuContent = OpenSetPlaybackRangeMenu(MyGeometry, MouseEvent); FSlateApplication::Get().PushMenu( WidgetOwner.AsShared(), MouseEvent.GetEventPath() != nullptr ? *MouseEvent.GetEventPath() : FWidgetPath(), MenuContent, MouseEvent.GetScreenSpacePosition(), FPopupTransitionEffect( FPopupTransitionEffect::ContextMenu ) ); return FReply::Handled().SetUserFocus(MenuContent, EFocusCause::SetDirectly).ReleaseMouseCapture(); } // return unhandled in case our parent wants to use our right mouse button to open a context menu if (DistanceDragged == 0.f) { return FReply::Unhandled().ReleaseMouseCapture(); } } bPanning = false; bMouseDownInRegion = false; MouseDownPosition[0].Reset(); MouseDownPosition[1].Reset(); return FReply::Handled().ReleaseMouseCapture(); } else if ( bHandleLeftMouseButton || bHandleMiddleMouseButton ) { if (MouseDragType == DRAG_PLAYBACK_START) { TimeSliderArgs.OnPlaybackRangeEndDrag.ExecuteIfBound(); } else if (MouseDragType == DRAG_PLAYBACK_END) { TimeSliderArgs.OnPlaybackRangeEndDrag.ExecuteIfBound(); } else if (MouseDragType == DRAG_SELECTION_START) { TimeSliderArgs.OnSelectionRangeEndDrag.ExecuteIfBound(); } else if (MouseDragType == DRAG_SELECTION_END) { TimeSliderArgs.OnSelectionRangeEndDrag.ExecuteIfBound(); } else if (MouseDragType == DRAG_MARK) { TimeSliderArgs.OnMarkEndDrag.ExecuteIfBound(); } else if (MouseDragType == DRAG_SETTING_RANGE && MouseDownPosition[0].IsSet()) { // Zooming FFrameTime MouseDownStart = ComputeFrameTimeFromMouse(MyGeometry, MouseDownPosition[0].GetValue(), RangeToScreen); const bool bCanZoomIn = MouseTime > MouseDownStart; const bool bCanZoomOut = ViewRangeStack.Num() > 0; if (bCanZoomIn || bCanZoomOut) { TRange ViewRange = GetViewRange(); if (!bCanZoomIn) { ViewRange = ViewRangeStack.Pop(); } if (bCanZoomIn) { // push the current value onto the stack ViewRangeStack.Add(ViewRange); ViewRange = TRange(MouseDownStart.FrameNumber / GetTickResolution(), MouseTime.FrameNumber / GetTickResolution()); } TimeSliderArgs.OnViewRangeChanged.ExecuteIfBound(ViewRange, EViewRangeInterpolation::Immediate); if( !TimeSliderArgs.ViewRange.IsBound() ) { // The output is not bound to a delegate so we'll manage the value ourselves TimeSliderArgs.ViewRange.Set(ViewRange); } } } else if (bMouseDownInRegion) { TSharedPtr Sequencer = WeakSequencer.Pin(); if (HoverMarkIndex == INDEX_NONE) { FFrameTime ScrubTime = MouseTime; FVector2D CursorPos = MouseEvent.GetScreenSpacePosition(); if (MouseDragType == DRAG_SCRUBBING_TIME) { ScrubTime = ComputeScrubTimeFromMouse(MyGeometry, MouseEvent, RangeToScreen); } else if (Sequencer.IsValid()) { ScrubTime = SnapTimeToNearestKey(MouseEvent, RangeToScreen, CursorPos.X, ScrubTime); // We weren't scrubbing, so this is a single click in the time slider. This should // also act as a deselection of any selected markers. Sequencer->GetViewModel()->GetSelection()->MarkedFrames.Empty(); } // If middle mouse button down we don't evaluate on the time change CommitScrubPosition( ScrubTime, /*bIsScrubbing=*/false , /*bEvaluate*/ !bHandleMiddleMouseButton); // Teleport the playhead to the clicked time. TimeSliderArgs.OnEndScrubberMovement.ExecuteIfBound(); } else if (Sequencer.IsValid()) { // Select the clicked marker, and optionally teleport the playhead. HandleMarkSelection(HoverMarkIndex); FModifierKeysState ModifierKeys = FSlateApplication::Get().GetModifierKeys(); const bool bSnapPlayTimeToMark = ModifierKeys.AreModifersDown(EModifierKey::Shift); if (bSnapPlayTimeToMark || Sequencer->GetSequencerSettings()->GetSnapPlayTimeToPressedKey()) { const TArray& MarkedFrames = TimeSliderArgs.MarkedFrames.Get(); const FFrameNumber MarkTime = MarkedFrames[HoverMarkIndex].FrameNumber; // If middle mouse button down we don't evaluate on the time change CommitScrubPosition(MarkTime, /*bIsScrubbing=*/ false , /*bEvaluate*/ !bHandleMiddleMouseButton); } } } MouseDragType = DRAG_NONE; DistanceDragged = 0.f; bMouseDownInRegion = false; MouseDownPosition[0].Reset(); MouseDownPosition[1].Reset(); return FReply::Handled().ReleaseMouseCapture(); } bMouseDownInRegion = false; MouseDownPosition[0].Reset(); MouseDownPosition[1].Reset(); return FReply::Unhandled(); } FReply FSequencerTimeSliderController::OnMouseButtonDoubleClick(SWidget& WidgetOwner, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { if (HoverMarkIndex != INDEX_NONE) { // Teleport the playhead to the double-clicked marker. bool bHandleMiddleMouseButton = MouseEvent.GetEffectingButton() == EKeys::MiddleMouseButton && WidgetOwner.HasMouseCapture(); const TArray& MarkedFrames = TimeSliderArgs.MarkedFrames.Get(); const FFrameNumber MarkTime = MarkedFrames[HoverMarkIndex].FrameNumber; CommitScrubPosition(MarkTime, /*bIsScrubbing=*/ false , /*bEvaluate*/ !bHandleMiddleMouseButton); } return FReply::Unhandled(); } double FSequencerTimeSliderController::ComputeHeight() const { using namespace UE::Sequencer; TSharedPtr Sequencer = WeakSequencer.Pin(); if (Sequencer.IsValid()) { TViewModelPtr Clock = Sequencer->GetViewModel()->GetRootSequenceModel().ImplicitCast(); if (Clock) { return Clock->GetDesiredTimeSliderHeight(Sequencer); } } return 22.0; } FReply FSequencerTimeSliderController::OnMouseMove( SWidget& WidgetOwner, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) { return OnMouseMoveImpl(WidgetOwner, MyGeometry, MouseEvent, false); } FReply FSequencerTimeSliderController::OnTimeSliderMouseMove(SWidget& WidgetOwner, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { return OnMouseMoveImpl(WidgetOwner, MyGeometry, MouseEvent, true); } FReply FSequencerTimeSliderController::OnMouseMoveImpl( SWidget& WidgetOwner, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, bool bFromTimeSlider ) { using namespace UE::Sequencer; TSharedPtr Sequencer = WeakSequencer.Pin(); if (!Sequencer.IsValid()) { return FReply::Unhandled(); } bool bHandleLeftMouseButton = MouseEvent.IsMouseButtonDown( EKeys::LeftMouseButton ); bool bHandleRightMouseButton = MouseEvent.IsMouseButtonDown( EKeys::RightMouseButton ) && TimeSliderArgs.AllowZoom; bool bHandleMiddleMouseButton = MouseEvent.IsMouseButtonDown(EKeys::MiddleMouseButton); const bool bLockedMarkedFrames = TimeSliderArgs.AreMarkedFramesLocked.Get(); HoverMarkIndex = INDEX_NONE; int32 DragMarkIndex = INDEX_NONE; if (bHandleRightMouseButton) { if (!bPanning) { DistanceDragged += FMath::Abs( MouseEvent.GetCursorDelta().X ); if ( DistanceDragged > 0.f /*FSlateApplication::Get().GetDragTriggerDistance()*/ ) { bPanning = true; } } else if (MouseEvent.IsShiftDown() && MouseEvent.IsAltDown()) { float MouseFractionX = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition()).X / MyGeometry.GetLocalSize().X; // If zooming on the current time, adjust mouse fractionX if (Sequencer->GetSequencerSettings()->GetZoomPosition() == ESequencerZoomPosition::SZP_CurrentTime) { const double ScrubPosition = TimeSliderArgs.ScrubPosition.Get() / GetTickResolution(); if (GetViewRange().Contains(ScrubPosition)) { FScrubRangeToScreen RangeToScreen(GetViewRange(), MyGeometry.Size); float TimePosition = RangeToScreen.InputToLocalX(ScrubPosition); MouseFractionX = TimePosition / MyGeometry.GetLocalSize().X; } } const float ZoomDelta = -0.01f * MouseEvent.GetCursorDelta().X; ZoomByDelta(ZoomDelta, MouseFractionX); } else { TRange LocalViewRange = GetViewRange(); double LocalViewRangeMin = LocalViewRange.GetLowerBoundValue(); double LocalViewRangeMax = LocalViewRange.GetUpperBoundValue(); FScrubRangeToScreen ScaleInfo( LocalViewRange, MyGeometry.Size ); FVector2D ScreenDelta = MouseEvent.GetCursorDelta(); FVector2D InputDelta; InputDelta.X = ScreenDelta.X/ScaleInfo.PixelsPerInput; double NewViewOutputMin = LocalViewRangeMin - InputDelta.X; double NewViewOutputMax = LocalViewRangeMax - InputDelta.X; ClampViewRange(NewViewOutputMin, NewViewOutputMax); SetViewRange(NewViewOutputMin, NewViewOutputMax, EViewRangeInterpolation::Immediate); } } else if ((bHandleLeftMouseButton || bHandleMiddleMouseButton) && MouseDownPosition[0].IsSet()) { TRange LocalViewRange = GetViewRange(); FScrubRangeToScreen RangeToScreen(LocalViewRange, MyGeometry.Size); DistanceDragged += FMath::Abs( MouseEvent.GetCursorDelta().X ); if ( MouseDragType == DRAG_NONE ) { if ( DistanceDragged > 0.f /*FSlateApplication::Get().GetDragTriggerDistance()*/ ) { FFrameTime MouseDownFree = ComputeFrameTimeFromMouse(MyGeometry, MouseDownPosition[0].GetValue(), RangeToScreen, false); const bool bReadOnly = Sequencer->IsReadOnly(); const FFrameRate TickResolution = GetTickResolution(); const bool bLockedPlayRange = TimeSliderArgs.IsPlaybackRangeLocked.Get(); const float MouseDownPixel = RangeToScreen.InputToLocalX(MouseDownFree / TickResolution); const bool bHitScrubber = GetHitTestScrubPixelMetrics(RangeToScreen).HandleRangePx.Contains(MouseDownPixel); TRange SelectionRange = TimeSliderArgs.SelectionRange.Get() / TickResolution; TRange PlaybackRange = TimeSliderArgs.PlaybackRange.Get() / TickResolution; // Disable selection range test if it's empty so that the playback range scrubbing gets priority if (!SelectionRange.IsEmpty() && !bHitScrubber && HitTestRangeEnd(RangeToScreen, SelectionRange, MouseDownPixel) && bHandleMiddleMouseButton == false) { // selection range end scrubber MouseDragType = DRAG_SELECTION_END; TimeSliderArgs.OnSelectionRangeBeginDrag.ExecuteIfBound(); } else if (!SelectionRange.IsEmpty() && !bHitScrubber && HitTestRangeStart(RangeToScreen, SelectionRange, MouseDownPixel) && bHandleMiddleMouseButton == false) { // selection range start scrubber MouseDragType = DRAG_SELECTION_START; TimeSliderArgs.OnSelectionRangeBeginDrag.ExecuteIfBound(); } else if (!bLockedPlayRange && !bHitScrubber && HitTestRangeEnd(RangeToScreen, PlaybackRange, MouseDownPixel) && bHandleMiddleMouseButton == false) { // playback range end scrubber MouseDragType = DRAG_PLAYBACK_END; TimeSliderArgs.OnPlaybackRangeBeginDrag.ExecuteIfBound(); } else if (!bLockedPlayRange && !bHitScrubber && HitTestRangeStart(RangeToScreen, PlaybackRange, MouseDownPixel) && bHandleMiddleMouseButton == false) { // playback range start scrubber MouseDragType = DRAG_PLAYBACK_START; TimeSliderArgs.OnPlaybackRangeBeginDrag.ExecuteIfBound(); } else if (!bLockedMarkedFrames && !bHitScrubber && HitTestMark(MyGeometry, RangeToScreen, MouseDownPixel, bFromTimeSlider, &DragMarkIndex) && bHandleMiddleMouseButton == false) { MouseDragType = DRAG_MARK; HandleMarkSelection(DragMarkIndex); DragMarkMap.Empty(); const FMarkedFrameSelection& SelectedMarkedFrames = Sequencer->GetViewModel()->GetSelection()->MarkedFrames; const TArray& MarkedFrames = TimeSliderArgs.MarkedFrames.Get(); for (TSet::TConstIterator It = SelectedMarkedFrames.GetSelected(); It; ++It) { const int32 MarkIndex = *It; const FMovieSceneMarkedFrame& MarkedFrame = MarkedFrames[MarkIndex]; DragMarkMap.Add(MarkIndex, MarkedFrame.FrameNumber); } TimeSliderArgs.OnMarkBeginDrag.ExecuteIfBound(); } else if (FSlateApplication::Get().GetModifierKeys().AreModifersDown(EModifierKey::Control) && bHandleMiddleMouseButton == false) { MouseDragType = DRAG_SETTING_RANGE; } else if (bMouseDownInRegion) { MouseDragType = DRAG_SCRUBBING_TIME; TimeSliderArgs.OnBeginScrubberMovement.ExecuteIfBound(); } } } else { FFrameTime MouseTime = ComputeFrameTimeFromMouse(MyGeometry, MouseEvent.GetScreenSpacePosition(), RangeToScreen); FFrameTime ScrubTime = ComputeScrubTimeFromMouse(MyGeometry, MouseEvent, RangeToScreen); FFrameTime MouseDownTime = ComputeFrameTimeFromMouse(MyGeometry, MouseDownPosition[0].GetValue(), RangeToScreen); FFrameNumber DiffFrame = MouseTime.FrameNumber - MouseDownTime.FrameNumber; // Set the start range time? if (MouseDragType == DRAG_PLAYBACK_START) { if (MouseEvent.IsShiftDown()) { SetPlaybackRangeStart(MouseDownPlaybackRange.GetLowerBoundValue() + DiffFrame); SetPlaybackRangeEnd(MouseDownPlaybackRange.GetUpperBoundValue() + DiffFrame); } else { SetPlaybackRangeStart(MouseTime.FrameNumber); } } // Set the end range time? else if(MouseDragType == DRAG_PLAYBACK_END) { if (MouseEvent.IsShiftDown()) { SetPlaybackRangeStart(MouseDownPlaybackRange.GetLowerBoundValue() + DiffFrame); SetPlaybackRangeEnd(MouseDownPlaybackRange.GetUpperBoundValue() + DiffFrame); } else { SetPlaybackRangeEnd(MouseTime.FrameNumber); } } else if (MouseDragType == DRAG_SELECTION_START) { if (MouseEvent.IsShiftDown()) { SetSelectionRangeStart(MouseDownSelectionRange.GetLowerBoundValue() + DiffFrame); SetSelectionRangeEnd(MouseDownSelectionRange.GetUpperBoundValue() + DiffFrame); } else { SetSelectionRangeStart(MouseTime.FrameNumber); } } // Set the end range time? else if(MouseDragType == DRAG_SELECTION_END) { if (MouseEvent.IsShiftDown()) { SetSelectionRangeStart(MouseDownSelectionRange.GetLowerBoundValue() + DiffFrame); SetSelectionRangeEnd(MouseDownSelectionRange.GetUpperBoundValue() + DiffFrame); } else { SetSelectionRangeEnd(MouseTime.FrameNumber); } } else if (MouseDragType == DRAG_MARK) { SetMark(DiffFrame); } else if (MouseDragType == DRAG_SCRUBBING_TIME) { // Delegate responsibility for clamping to the current viewrange to the client CommitScrubPosition( ScrubTime, /*bIsScrubbing=*/true, /*bEvaluate*/ !bHandleMiddleMouseButton); //if middle mouse button down we don't evaluate on the time change } else if (MouseDragType == DRAG_SETTING_RANGE) { MouseDownPosition[1] = MouseEvent.GetScreenSpacePosition(); } } } else if (bFromTimeSlider && !bLockedMarkedFrames && DragMarkIndex == INDEX_NONE) { // Update hover state of marked frames. TRange LocalViewRange = GetViewRange(); FScrubRangeToScreen RangeToScreen(LocalViewRange, MyGeometry.Size); const FVector2D LocalMousePostion = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition()); HitTestMark(MyGeometry, RangeToScreen, LocalMousePostion.X, bFromTimeSlider, &HoverMarkIndex); } if ( DistanceDragged != 0.f && (bHandleLeftMouseButton || bHandleRightMouseButton) ) { return FReply::Handled().CaptureMouse(WidgetOwner.AsShared()); } return FReply::Handled(); } void FSequencerTimeSliderController::CommitScrubPosition( FFrameTime NewValue, bool bIsScrubbing, bool bEvaluate) { using namespace UE::Sequencer; bIsEvaluating = bEvaluate; // The user can scrub past the viewing range of the time slider controller, so we clamp it to the view range. TSharedPtr Sequencer = WeakSequencer.Pin(); if(!Sequencer.IsValid()) { return; } const bool bIsWarped = Sequencer->GetSequencerSettings()->GetTimeWarpDisplayMode() == ESequencerTimeWarpDisplay::WarpedTime; FTimeDomainOverride TimeDomainOverride = Sequencer->OverrideTimeDomain(bIsWarped ? ETimeDomain::Warped : ETimeDomain::Unwarped); Sequencer->SnapSequencerTime(NewValue); if (bIsScrubbing) { FAnimatedRange ViewRange = GetViewRange(); FFrameRate DisplayRate = Sequencer->GetFocusedDisplayRate(); FFrameRate TickResolution = Sequencer->GetFocusedTickResolution(); FFrameTime LowerBound = (ViewRange.GetLowerBoundValue() * TickResolution).CeilToFrame(); FFrameTime UpperBound = (ViewRange.GetUpperBoundValue() * TickResolution).FloorToFrame(); if (Sequencer->GetSequencerSettings()->GetForceWholeFrames()) { LowerBound = FFrameRate::Snap(LowerBound, TickResolution, DisplayRate); UpperBound = FFrameRate::Snap(UpperBound, TickResolution, DisplayRate); } NewValue = FMath::Clamp(NewValue, LowerBound, UpperBound); } // Manage the scrub position ourselves if its not bound to a delegate if ( !TimeSliderArgs.ScrubPosition.IsBound() ) { TimeSliderArgs.ScrubPosition.Set( NewValue ); } TimeSliderArgs.OnScrubPositionChanged.ExecuteIfBound( NewValue, bIsScrubbing, bEvaluate); } FReply FSequencerTimeSliderController::OnMouseWheel( SWidget& WidgetOwner, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) { TOptional> NewTargetRange; if ( TimeSliderArgs.AllowZoom && MouseEvent.IsControlDown() ) { float MouseFractionX = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition()).X / MyGeometry.GetLocalSize().X; TSharedPtr Sequencer = WeakSequencer.Pin(); // If zooming on the current time, adjust mouse fractionX if (Sequencer.IsValid() && Sequencer->GetSequencerSettings()->GetZoomPosition() == ESequencerZoomPosition::SZP_CurrentTime) { const double ScrubPosition = TimeSliderArgs.ScrubPosition.Get() / GetTickResolution(); if (GetViewRange().Contains(ScrubPosition)) { FScrubRangeToScreen RangeToScreen(GetViewRange(), MyGeometry.Size); float TimePosition = RangeToScreen.InputToLocalX(ScrubPosition); MouseFractionX = TimePosition / MyGeometry.GetLocalSize().X; } } const float ZoomDelta = -0.2f * MouseEvent.GetWheelDelta(); if (ZoomByDelta(ZoomDelta, MouseFractionX)) { return FReply::Handled(); } } else if (MouseEvent.IsShiftDown()) { PanByDelta(-MouseEvent.GetWheelDelta()); return FReply::Handled(); } return FReply::Unhandled(); } FCursorReply FSequencerTimeSliderController::OnCursorQuery( TSharedRef WidgetOwner, const FGeometry& MyGeometry, const FPointerEvent& CursorEvent ) const { TSharedPtr Sequencer = WeakSequencer.Pin(); if (!Sequencer.IsValid()) { return FCursorReply::Unhandled(); } FScrubRangeToScreen RangeToScreen(GetViewRange(), MyGeometry.Size); const bool bReadOnly = Sequencer->IsReadOnly(); const FFrameRate TickResolution = GetTickResolution(); const bool bLockedPlayRange = TimeSliderArgs.IsPlaybackRangeLocked.Get(); const bool bLockedMarkedFrames = TimeSliderArgs.AreMarkedFramesLocked.Get(); const float HitTestPixel = MyGeometry.AbsoluteToLocal(CursorEvent.GetScreenSpacePosition()).X; const bool bHitScrubber = GetHitTestScrubPixelMetrics(RangeToScreen).HandleRangePx.Contains(HitTestPixel); TRange SelectionRange = TimeSliderArgs.SelectionRange.Get() / TickResolution; TRange PlaybackRange = TimeSliderArgs.PlaybackRange.Get() / TickResolution; if (MouseDragType == DRAG_SCRUBBING_TIME) { return FCursorReply::Unhandled(); } // Use L/R resize cursor if we're dragging or hovering a playback range bound if ((MouseDragType == DRAG_PLAYBACK_END) || (MouseDragType == DRAG_PLAYBACK_START) || (MouseDragType == DRAG_SELECTION_START) || (MouseDragType == DRAG_SELECTION_END) || (!bLockedPlayRange && !bHitScrubber && HitTestRangeStart(RangeToScreen, PlaybackRange, HitTestPixel)) || (!bLockedPlayRange && !bHitScrubber && HitTestRangeEnd( RangeToScreen, PlaybackRange, HitTestPixel)) || (!SelectionRange.IsEmpty() && !bHitScrubber && HitTestRangeStart(RangeToScreen, SelectionRange, HitTestPixel)) || (!SelectionRange.IsEmpty() && !bHitScrubber && HitTestRangeEnd( RangeToScreen, SelectionRange, HitTestPixel))) { return FCursorReply::Cursor(EMouseCursor::ResizeLeftRight); } const bool bFromTimeSlider = true; if (MouseDragType == DRAG_MARK || (!bLockedMarkedFrames && !bHitScrubber && HitTestMark(MyGeometry, RangeToScreen, HitTestPixel, bFromTimeSlider))) { return FCursorReply::Cursor(EMouseCursor::CardinalCross); } return FCursorReply::Unhandled(); } int32 FSequencerTimeSliderController::OnPaintViewArea( const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, bool bEnabled, const FPaintViewAreaArgs& Args ) const { using namespace UE::MovieScene; using namespace UE::Sequencer; TSharedPtr Sequencer = WeakSequencer.Pin(); if (!Sequencer.IsValid()) { return LayerId; } const ESlateDrawEffect DrawEffects = bEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect; TRange LocalViewRange = GetViewRange(); FScrubRangeToScreen RangeToScreen( LocalViewRange, AllottedGeometry.Size ); if (Args.PlaybackRangeArgs.IsSet()) { FPaintPlaybackRangeArgs PaintArgs = Args.PlaybackRangeArgs.GetValue(); LayerId = DrawPlaybackRange(AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, RangeToScreen, PaintArgs); LayerId = DrawSubSequenceRange(AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, RangeToScreen, PaintArgs); PaintArgs.SolidFillOpacity = 0.f; LayerId = DrawSelectionRange(AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, RangeToScreen, PaintArgs); } if( Args.bDisplayTickLines ) { static FLinearColor TickColor(0.f, 0.f, 0.f, 0.3f); // Draw major tick lines in the section area FDrawTickArgs DrawTickArgs; { DrawTickArgs.AllottedGeometry = AllottedGeometry; DrawTickArgs.bMirrorLabels = false; DrawTickArgs.bOnlyDrawMajorTicks = true; DrawTickArgs.TickColor = TickColor; DrawTickArgs.CullingRect = MyCullingRect; DrawTickArgs.DrawEffects = DrawEffects; // Draw major ticks under sections DrawTickArgs.StartLayer = LayerId-1; // Draw the tick the entire height of the section area DrawTickArgs.TickOffset = 0.0f; DrawTickArgs.MajorTickHeight = AllottedGeometry.Size.Y; } DrawTicks( OutDrawElements, LocalViewRange, RangeToScreen, DrawTickArgs ); } if (Args.bDisplayMarkedFrames) { LayerId = DrawMarkedFrames(AllottedGeometry, RangeToScreen, OutDrawElements, LayerId, DrawEffects, FWidgetStyle(), false); } LayerId = DrawScalingAnchors(AllottedGeometry, RangeToScreen, OutDrawElements, LayerId, DrawEffects); LayerId = DrawVerticalFrames(AllottedGeometry, RangeToScreen, OutDrawElements, LayerId, DrawEffects); FTimeWarpTrackExtension* TimeWarpExtension = Sequencer->GetViewModel()->GetRootSequenceModel()->GetSharedData()->CastDynamic(); const FTimeWarpTrackModel* ActiveTimeWarpTrack = TimeWarpExtension ? TimeWarpExtension->GetActiveTimeWarpTrack() : nullptr; FLinearColor TimeWarpColor = FStyleColors::AccentOrange.GetSpecifiedColor(); // If we have no active timewarp track, just draw the single scrub position if (ActiveTimeWarpTrack == nullptr || Sequencer->GetSequencerSettings()->GetTimeWarpDisplayMode() != ESequencerTimeWarpDisplay::Both) { const bool bIsWarped = (Sequencer->GetSequencerSettings()->GetTimeWarpDisplayMode() == ESequencerTimeWarpDisplay::WarpedTime && ActiveTimeWarpTrack != nullptr); FLinearColor ScrubColor = bIsWarped ? TimeWarpColor : FLinearColor::White; FQualifiedFrameTime ScrubPosition = FQualifiedFrameTime(TimeSliderArgs.ScrubPosition.Get(), GetTickResolution()); FScrubberMetrics ScrubMetrics = GetScrubPixelMetrics(ScrubPosition, RangeToScreen); if (ScrubMetrics.bDrawExtents) { FSlateDrawElement::MakeBox( OutDrawElements, LayerId + 1, AllottedGeometry.ToPaintGeometry( FVector2f(ScrubMetrics.FrameExtentsPx.Size(), AllottedGeometry.Size.Y), FSlateLayoutTransform(FVector2f(ScrubMetrics.FrameExtentsPx.GetLowerBoundValue(), 0.f))), ScrubFillBrush, DrawEffects, ScrubColor.CopyWithNewOpacity(0.5f) ); } if (Args.bDisplayScrubPosition) { const float ScrubPixelX = RangeToScreen.InputToLocalX(ScrubPosition.AsSeconds()); TArray LinePoints; LinePoints.AddUninitialized(2); LinePoints[0] = FVector2f( ScrubPixelX, 0.f ); LinePoints[1] = FVector2f( ScrubPixelX, AllottedGeometry.Size.Y ); // Draw a white line for the unwarped scrub position FSlateDrawElement::MakeLines( OutDrawElements, LayerId+1, AllottedGeometry.ToPaintGeometry(), LinePoints, DrawEffects, ScrubColor, false ); } } else { // Handle timewarp by drawing the white (unwarped) position up to the bottom of the active timewarp track, // then draw the orange (warped) time from there FVirtualGeometry Geometry = ActiveTimeWarpTrack->GetVirtualGeometry(); const float UnwarpedScrubVerticalClip = StaticCastSharedRef(Sequencer->GetSequencerWidget())->GetPinnedTreeView()->VirtualToPhysical(Geometry.GetTop() + Geometry.GetNestedHeight()); FQualifiedFrameTime UnwarpedScrubPosition = Sequencer->GetUnwarpedLocalTime(); FQualifiedFrameTime WarpedScrubPosition = Sequencer->GetLocalTime(); FScrubberMetrics UnwarpedScrubMetrics = GetScrubPixelMetrics(UnwarpedScrubPosition, RangeToScreen); FScrubberMetrics WarpedScrubMetrics = GetScrubPixelMetrics(WarpedScrubPosition, RangeToScreen); if (UnwarpedScrubMetrics.bDrawExtents) { // Draw a box for the unwarped scrub position FSlateDrawElement::MakeBox( OutDrawElements, LayerId+1, AllottedGeometry.ToPaintGeometry( FVector2f(UnwarpedScrubMetrics.FrameExtentsPx.Size(), UnwarpedScrubVerticalClip - 1.f), FSlateLayoutTransform(FVector2f(UnwarpedScrubMetrics.FrameExtentsPx.GetLowerBoundValue(), 0.f))), ScrubFillBrush, DrawEffects, FLinearColor::White.CopyWithNewOpacity(0.5f) ); // Draw a box for the warped scrub position FSlateDrawElement::MakeBox( OutDrawElements, LayerId + 1, AllottedGeometry.ToPaintGeometry( FVector2f(WarpedScrubMetrics.FrameExtentsPx.Size(), AllottedGeometry.Size.Y - UnwarpedScrubVerticalClip), FSlateLayoutTransform(FVector2f(WarpedScrubMetrics.FrameExtentsPx.GetLowerBoundValue(), UnwarpedScrubVerticalClip))), ScrubFillBrush, DrawEffects, TimeWarpColor.CopyWithNewOpacity(0.75f) ); } if (Args.bDisplayScrubPosition) { const float UnwarpedScrubPixelX = RangeToScreen.InputToLocalX(UnwarpedScrubPosition.AsSeconds()); const float WarpedScrubPixelX = RangeToScreen.InputToLocalX(WarpedScrubPosition.AsSeconds()); const float OriginPixelX = RangeToScreen.InputToLocalX(0.f); TArray LinePoints; LinePoints.AddUninitialized(2); LinePoints[0] = FVector2f( UnwarpedScrubPixelX, 0.f ); LinePoints[1] = FVector2f( UnwarpedScrubPixelX, UnwarpedScrubVerticalClip-1.f ); // Draw a white line for the unwarped scrub position FSlateDrawElement::MakeLines( OutDrawElements, LayerId+1, AllottedGeometry.ToPaintGeometry(), LinePoints, DrawEffects, FLinearColor(1.f, 1.f, 1.f, .5f), false ); LinePoints[0] = FVector2f( WarpedScrubPixelX, UnwarpedScrubVerticalClip ); LinePoints[1] = FVector2f( WarpedScrubPixelX, AllottedGeometry.Size.Y ); // Draw a line for the warped scrub position FSlateDrawElement::MakeLines( OutDrawElements, LayerId+1, AllottedGeometry.ToPaintGeometry(), LinePoints, DrawEffects, TimeWarpColor, false ); // Optional cvar for drawing the dashed line if (GSequencerShowTimeWarpScrubberLink) { const float StartLine = WarpedScrubPixelX < UnwarpedScrubPixelX ? WarpedScrubPixelX : UnwarpedScrubPixelX; const float EndLine = WarpedScrubPixelX < UnwarpedScrubPixelX ? UnwarpedScrubPixelX : WarpedScrubPixelX; const float HalfPos = (EndLine + StartLine)*.5f; // Draw a dashed line to connect them LinePoints[0] = FVector2f( StartLine, UnwarpedScrubVerticalClip-1.f ); LinePoints[1] = FVector2f( FMath::Max(HalfPos - 10.f, StartLine), UnwarpedScrubVerticalClip-1.f ); FSlateDrawElement::MakeDashedLines( OutDrawElements, LayerId++, AllottedGeometry.ToPaintGeometry(), CopyTemp(LinePoints), DrawEffects, FColor(212, 147, 20), 2.f, 5.f, StartLine - OriginPixelX ); LinePoints[0] = FVector2f( FMath::Min(HalfPos + 10.f, EndLine), UnwarpedScrubVerticalClip-1.f ); LinePoints[1] = FVector2f( EndLine, UnwarpedScrubVerticalClip-1.f ); FSlateDrawElement::MakeDashedLines( OutDrawElements, LayerId++, AllottedGeometry.ToPaintGeometry(), CopyTemp(LinePoints), DrawEffects, FColor(212, 147, 20), 2.f, 5.f, StartLine - OriginPixelX ); FSlateDrawElement::MakeBox( OutDrawElements, LayerId + 1, AllottedGeometry.ToPaintGeometry( FVector2f(12.f, 12.f), FSlateLayoutTransform(FVector2f( HalfPos-6.f, UnwarpedScrubVerticalClip-6.f )) ), FAppStyle::GetBrush( TEXT( "Sequencer.Tracks.TimeWarp" ) ), DrawEffects, TimeWarpColor.CopyWithNewOpacity(0.75f) ); } } } return LayerId; } void FSequencerTimeSliderController::SetPlaybackStatus(ETimeSliderPlaybackStatus InStatus) { using namespace UE::Sequencer; if (!WeakSequencer.IsValid()) { return; } switch (InStatus) { case ETimeSliderPlaybackStatus::Jumping: { WeakSequencer.Pin()->SetPlaybackStatus(EMovieScenePlayerStatus::Jumping); break; } case ETimeSliderPlaybackStatus::Paused: { WeakSequencer.Pin()->SetPlaybackStatus(EMovieScenePlayerStatus::Paused); break; } case ETimeSliderPlaybackStatus::Playing: { WeakSequencer.Pin()->SetPlaybackStatus(EMovieScenePlayerStatus::Playing); break; } case ETimeSliderPlaybackStatus::Scrubbing: { WeakSequencer.Pin()->SetPlaybackStatus(EMovieScenePlayerStatus::Scrubbing); break; } case ETimeSliderPlaybackStatus::Stepping: { WeakSequencer.Pin()->SetPlaybackStatus(EMovieScenePlayerStatus::Stepping); break; } default: case ETimeSliderPlaybackStatus::Stopped: { WeakSequencer.Pin()->SetPlaybackStatus(EMovieScenePlayerStatus::Stopped); break; } } } ETimeSliderPlaybackStatus FSequencerTimeSliderController::GetPlaybackStatus() const { using namespace UE::Sequencer; if (!WeakSequencer.IsValid()) { return ETimeSliderPlaybackStatus::Stopped; } EMovieScenePlayerStatus::Type Status = WeakSequencer.Pin()->GetPlaybackStatus(); switch (Status) { case EMovieScenePlayerStatus::Jumping: { return ETimeSliderPlaybackStatus::Jumping; } case EMovieScenePlayerStatus::Paused: { return ETimeSliderPlaybackStatus::Paused; } case EMovieScenePlayerStatus::Playing: { return ETimeSliderPlaybackStatus::Playing; } case EMovieScenePlayerStatus::Scrubbing: { return ETimeSliderPlaybackStatus::Scrubbing; } case EMovieScenePlayerStatus::Stepping: { return ETimeSliderPlaybackStatus::Stepping; } case EMovieScenePlayerStatus::Stopped: { return ETimeSliderPlaybackStatus::Stopped; } } return ETimeSliderPlaybackStatus::Stopped; } TSharedRef FSequencerTimeSliderController::OpenSetPlaybackRangeMenu(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { using namespace UE::Sequencer; class SMarkedFramePropertyWidget : public SCompoundWidget, public FNotifyHook { public: UMovieScene* MovieSceneToModify; TSharedPtr DetailsView; TWeakPtr WeakSequencer; SLATE_BEGIN_ARGS(SMarkedFramePropertyWidget){} SLATE_END_ARGS() void Construct(const FArguments& InArgs, UMovieScene* InMovieScene, int32 InMarkedFrameIndex, TWeakPtr InWeakSequencer) { MovieSceneToModify = InMovieScene; WeakSequencer = InWeakSequencer; FPropertyEditorModule& PropertyEditorModule = FModuleManager::GetModuleChecked("PropertyEditor"); FDetailsViewArgs DetailsViewArgs; DetailsViewArgs.bAllowSearch = false; DetailsViewArgs.bShowScrollBar = false; DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::HideNameArea; DetailsViewArgs.NotifyHook = this; FStructureDetailsViewArgs StructureDetailsViewArgs; StructureDetailsViewArgs.bShowObjects = true; StructureDetailsViewArgs.bShowAssets = true; StructureDetailsViewArgs.bShowClasses = true; StructureDetailsViewArgs.bShowInterfaces = true; TSharedPtr StructOnScope = MakeShared(FMovieSceneMarkedFrame::StaticStruct(), (uint8 *)&InMovieScene->GetMarkedFrames()[InMarkedFrameIndex]); DetailsView = PropertyEditorModule.CreateStructureDetailView(DetailsViewArgs, StructureDetailsViewArgs, nullptr); DetailsView->GetDetailsView()->RegisterInstancedCustomPropertyTypeLayout("FrameNumber", FOnGetPropertyTypeCustomizationInstance::CreateSP(WeakSequencer.Pin().ToSharedRef(), &FSequencer::MakeFrameNumberDetailsCustomization)); DetailsView->SetStructureData(StructOnScope); ChildSlot [ DetailsView->GetWidget().ToSharedRef() ]; } virtual void NotifyPreChange( FProperty* PropertyAboutToChange ) override { MovieSceneToModify->Modify(); } virtual void NotifyPreChange( class FEditPropertyChain* PropertyAboutToChange ) override { MovieSceneToModify->Modify(); } }; static const FName MenuName("Sequencer.TimelineMenu"); if (!UToolMenus::Get()->IsMenuRegistered(MenuName)) { UToolMenu* TimelineMenu = UToolMenus::Get()->RegisterMenu(MenuName, NAME_None, EMultiBoxType::Menu); TimelineMenu->bSearchable = false; TimelineMenu->bShouldCloseWindowAfterMenuSelection = true; TimelineMenu->AddDynamicSection(NAME_None, FNewToolMenuDelegate::CreateLambda([](UToolMenu* const InMenu) { USequencerTimeSliderControllerMenuContext* Context = InMenu->FindContext(); if (!Context) { return; } TSharedPtr TimeSliderController = Context->WeakTimeSliderController.Pin(); if (!TimeSliderController.IsValid()) { return; } TSharedPtr Sequencer = TimeSliderController->WeakSequencer.Pin(); if (!Sequencer.IsValid()) { return; } const bool bReadOnly = Sequencer->IsReadOnly(); const FGeometry& MyGeometry = Context->Geometry; const FPointerEvent& MouseEvent = Context->PointerEvent; const FFrameNumber& FrameNumber = Context->FrameNumber; FScrubRangeToScreen RangeToScreen = FScrubRangeToScreen(TimeSliderController->GetViewRange(), MyGeometry.Size); const float MousePixel = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition()).X; FText CurrentTimeText = FText::FromString(Sequencer->GetNumericTypeInterface()->ToString(FrameNumber.Value)); // SequencerPlaybackRangeMenu { FToolMenuSection& Section = InMenu->AddSection("SequencerPlaybackRangeMenu", FText::Format(LOCTEXT("PlaybackRangeTextFormat", "Playback Range ({0}):"), CurrentTimeText)); TRange PlaybackRange = TimeSliderController->TimeSliderArgs.PlaybackRange.Get(); TOptional> SubSequenceRange = TimeSliderController->TimeSliderArgs.SubSequenceRange.Get(); Section.AddMenuEntry( "SetPlaybackEnd", LOCTEXT("SetPlaybackEnd", "Set End Time"), FText(), FSlateIcon(), FUIAction( FExecuteAction::CreateLambda([TimeSliderController, FrameNumber] { TimeSliderController->SetPlaybackRangeEnd(FrameNumber); }), FCanExecuteAction::CreateLambda([TimeSliderController, FrameNumber, PlaybackRange] { return !TimeSliderController->TimeSliderArgs.IsPlaybackRangeLocked.Get() && FrameNumber >= UE::MovieScene::DiscreteInclusiveLower(PlaybackRange); }) ) ); Section.AddMenuEntry( "ConformToSubsequenceRange", LOCTEXT("ConformToSubsequenceRange", "Conform to Range"), LOCTEXT("ConformToSubsequenceRangeTooltip", "Conform the start and end time to the extents of the subsequence range"), FSlateIcon(), FUIAction( FExecuteAction::CreateLambda([TimeSliderController, SubSequenceRange] { TimeSliderController->SetPlaybackRangeStart(SubSequenceRange.GetValue().GetLowerBoundValue()); TimeSliderController->SetPlaybackRangeEnd(SubSequenceRange.GetValue().GetUpperBoundValue()); }), FCanExecuteAction::CreateLambda([TimeSliderController, SubSequenceRange] { return !TimeSliderController->TimeSliderArgs.IsPlaybackRangeLocked.Get() && SubSequenceRange.IsSet(); }) ) ); Section.AddMenuEntry( "ToggleLocked", LOCTEXT("ToggleLocked", "Locked"), LOCTEXT("ToggleLockedTooltip", "Lock/Unlock the playback range"), FSlateIcon(), FUIAction( FExecuteAction::CreateLambda([TimeSliderController] { TimeSliderController->TimeSliderArgs.OnTogglePlaybackRangeLocked.ExecuteIfBound(); }), FCanExecuteAction::CreateLambda([bReadOnly]{ return !bReadOnly; }), FIsActionChecked::CreateLambda([TimeSliderController] { return TimeSliderController->TimeSliderArgs.IsPlaybackRangeLocked.Get(); }) ), EUserInterfaceActionType::ToggleButton ); } // SequencerPlaybackRangeMenu { TRange SelectionRange = TimeSliderController->TimeSliderArgs.SelectionRange.Get(); FToolMenuSection& Section = InMenu->AddSection("SequencerSelectionRangeMenu", FText::Format(LOCTEXT("SelectionRangeTextFormat", "Playback Range ({0}):"), CurrentTimeText)); Section.AddMenuEntry( "SetSelectionStart", LOCTEXT("SetSelectionStart", "Set Selection Start"), FText(), FSlateIcon(), FUIAction( FExecuteAction::CreateLambda([TimeSliderController, FrameNumber] { TimeSliderController->SetSelectionRangeStart(FrameNumber); }), FCanExecuteAction::CreateLambda([SelectionRange, FrameNumber] { return SelectionRange.IsEmpty() || FrameNumber < UE::MovieScene::DiscreteExclusiveUpper(SelectionRange); }) ) ); Section.AddMenuEntry( "SetSelectionEnd", LOCTEXT("SetSelectionEnd", "Set Selection End"), FText(), FSlateIcon(), FUIAction( FExecuteAction::CreateLambda([TimeSliderController, FrameNumber] { TimeSliderController->SetSelectionRangeEnd(FrameNumber); }), FCanExecuteAction::CreateLambda([SelectionRange, FrameNumber] { return SelectionRange.IsEmpty() || FrameNumber >= UE::MovieScene::DiscreteInclusiveLower(SelectionRange); }) ) ); Section.AddMenuEntry( "ClearSelectionRange", LOCTEXT("ClearSelectionRange", "Clear Selection Range"), FText(), FSlateIcon(), FUIAction( FExecuteAction::CreateLambda([TimeSliderController] { TimeSliderController->TimeSliderArgs.OnSelectionRangeChanged.ExecuteIfBound(TRange::Empty()); }), FCanExecuteAction::CreateLambda([SelectionRange] { return !SelectionRange.IsEmpty(); }) ) ); } // SequencerParentChainMenu { UMovieSceneCompiledDataManager* CompiledDataManager = Sequencer->GetEvaluationTemplate().GetCompiledDataManager(); const FMovieSceneSequenceHierarchy* Hierarchy = CompiledDataManager->FindHierarchy(Sequencer->GetEvaluationTemplate().GetCompiledDataID()); if (Hierarchy) { FToolMenuSection& Section = InMenu->AddSection("SequencerParentChainMenu"); TArray ParentChain = TimeSliderController->TimeSliderArgs.ScrubPositionParentChain.Get(); for (FMovieSceneSequenceID ParentID : ParentChain) { FText ParentText = Sequencer->GetRootMovieSceneSequence()->GetDisplayName(); for (const TTuple& Pair : Hierarchy->AllSubSequenceData()) { if (Pair.Key == ParentID && Pair.Value.GetSequence()) { ParentText = Pair.Value.GetSequence()->GetDisplayName(); break; } } Section.AddMenuEntry( "DisplayTimeSpace", ParentText, FText::Format(LOCTEXT("DisplayTimeSpace", "Display time in the space of {0}"), ParentText), FSlateIcon(), FUIAction( FExecuteAction::CreateLambda([TimeSliderController, ParentID] { TimeSliderController->TimeSliderArgs.OnScrubPositionParentChanged.ExecuteIfBound(ParentID); }), FCanExecuteAction(), FIsActionChecked::CreateLambda([TimeSliderController, ParentID] { return TimeSliderController->TimeSliderArgs.ScrubPositionParent.Get() == MovieSceneSequenceID::Invalid ? ParentID == TimeSliderController->TimeSliderArgs.ScrubPositionParentChain.Get().Last() : TimeSliderController->TimeSliderArgs.ScrubPositionParent.Get() == ParentID; }) ), EUserInterfaceActionType::RadioButton ); } } } // SequencerMarkMenu { FToolMenuSection& Section = InMenu->AddSection("SequencerMarkMenu", FText::Format(LOCTEXT("MarkTextFormat", "Mark ({0}):"), CurrentTimeText)); UMovieScene* MovieScene = Sequencer->GetFocusedMovieSceneSequence()->GetMovieScene(); bool bHasMarks = MovieScene->GetMarkedFrames().Num() > 0; int32 MarkedIndex = INDEX_NONE; const bool bTestLabelBox = true; TimeSliderController->HitTestMark(MyGeometry, RangeToScreen, MousePixel, bTestLabelBox, &MarkedIndex); FSequencerSelection& SequencerSelection = Sequencer->GetSelection(); SequencerSelection.Empty(); if (MarkedIndex != INDEX_NONE) { SequencerSelection.MarkedFrames.Empty(); SequencerSelection.MarkedFrames.Select(MarkedIndex); const bool bLockedMarkedFrames = TimeSliderController->TimeSliderArgs.AreMarkedFramesLocked.Get(); TSharedRef Widget = SNew(SMarkedFramePropertyWidget, MovieScene, MarkedIndex, Sequencer.ToWeakPtr()); Widget->SetEnabled(!bLockedMarkedFrames); Section.AddEntry(FToolMenuEntry::InitWidget(NAME_None, Widget, FText::GetEmpty(), false)); } Section.AddMenuEntry( "AddMark", LOCTEXT("AddMark", "Add Mark"), FText(), FSlateIcon(), FUIAction( FExecuteAction::CreateLambda( [TimeSliderController, FrameNumber] { TimeSliderController->AddMarkAtFrame(FrameNumber); }), FCanExecuteAction::CreateLambda([TimeSliderController, MarkedIndex] { return !TimeSliderController->TimeSliderArgs.AreMarkedFramesLocked.Get() && MarkedIndex == INDEX_NONE; })) ); Section.AddMenuEntry( "DeleteMark", LOCTEXT("DeleteMark", "Delete Mark"), FText(), FSlateIcon(), FUIAction( FExecuteAction::CreateLambda([TimeSliderController, MarkedIndex] { TimeSliderController->DeleteMarkAtIndex(MarkedIndex); }), FCanExecuteAction::CreateLambda([TimeSliderController, MarkedIndex] { return !TimeSliderController->TimeSliderArgs.AreMarkedFramesLocked.Get() && MarkedIndex != INDEX_NONE; })) ); Section.AddMenuEntry( "DeleteAllMarks", LOCTEXT("DeleteAllMarks", "Delete All Marks"), FText(), FSlateIcon(), FUIAction( FExecuteAction::CreateLambda([TimeSliderController] { TimeSliderController->DeleteAllMarks(); }), FCanExecuteAction::CreateLambda([TimeSliderController, bHasMarks] { return !TimeSliderController->TimeSliderArgs.AreMarkedFramesLocked.Get() && bHasMarks; })) ); Section.AddMenuEntry( "ToggleLockedMarks", LOCTEXT("ToggleLockedMarks", "Locked"), LOCTEXT("ToggleLockedMarksTooltip", "Lock/Unlock all marked frames"), FSlateIcon(), FUIAction( FExecuteAction::CreateLambda([TimeSliderController] { TimeSliderController->TimeSliderArgs.OnToggleMarkedFramesLocked.ExecuteIfBound(); }), FCanExecuteAction::CreateLambda([bReadOnly]{ return !bReadOnly; }), FIsActionChecked::CreateLambda([TimeSliderController] { return TimeSliderController->TimeSliderArgs.AreMarkedFramesLocked.Get(); })), EUserInterfaceActionType::ToggleButton ); } })); } USequencerTimeSliderControllerMenuContext* TimeSliderContext = NewObject(); TimeSliderContext->WeakTimeSliderController = AsWeak(); TimeSliderContext->Geometry = MyGeometry; TimeSliderContext->PointerEvent = MouseEvent; TimeSliderContext->FrameNumber = ComputeFrameTimeFromMouse(MyGeometry, MouseEvent.GetScreenSpacePosition(), FScrubRangeToScreen(GetViewRange(), MyGeometry.Size)).FrameNumber; USequencerToolMenuContext* SequencerContext = NewObject(); SequencerContext->WeakSequencer = WeakSequencer; FToolMenuContext MenuContext; MenuContext.AddObject(TimeSliderContext); MenuContext.AddObject(SequencerContext); return UToolMenus::Get()->GenerateWidget(MenuName, MenuContext); } void FSequencerTimeSliderController::ClampViewRange(double& NewRangeMin, double& NewRangeMax) { bool bNeedsClampSet = false; double NewClampRangeMin = TimeSliderArgs.ClampRange.Get().GetLowerBoundValue(); if ( NewRangeMin < TimeSliderArgs.ClampRange.Get().GetLowerBoundValue() ) { NewClampRangeMin = NewRangeMin; bNeedsClampSet = true; } double NewClampRangeMax = TimeSliderArgs.ClampRange.Get().GetUpperBoundValue(); if ( NewRangeMax > TimeSliderArgs.ClampRange.Get().GetUpperBoundValue() ) { NewClampRangeMax = NewRangeMax; bNeedsClampSet = true; } if (bNeedsClampSet) { SetClampRange(NewClampRangeMin, NewClampRangeMax); } } void FSequencerTimeSliderController::SetViewRange( double NewRangeMin, double NewRangeMax, EViewRangeInterpolation Interpolation ) { // Clamp to a minimum size to avoid zero-sized or negative visible ranges double MinVisibleTimeRange = FFrameNumber(1) / GetTickResolution(); TRange ExistingViewRange = GetViewRange(); TRange ExistingClampRange = TimeSliderArgs.ClampRange.Get(); if (NewRangeMax == ExistingViewRange.GetUpperBoundValue()) { if (NewRangeMin > NewRangeMax - MinVisibleTimeRange) { NewRangeMin = NewRangeMax - MinVisibleTimeRange; } } else if (NewRangeMax < NewRangeMin + MinVisibleTimeRange) { NewRangeMax = NewRangeMin + MinVisibleTimeRange; } // Clamp to the clamp range const TRange NewRange = TRange::Intersection(TRange(NewRangeMin, NewRangeMax), ExistingClampRange); TimeSliderArgs.OnViewRangeChanged.ExecuteIfBound( NewRange, Interpolation ); if( !TimeSliderArgs.ViewRange.IsBound() ) { // The output is not bound to a delegate so we'll manage the value ourselves (no animation) TimeSliderArgs.ViewRange.Set( NewRange ); } } void FSequencerTimeSliderController::SetClampRange( double NewRangeMin, double NewRangeMax ) { const TRange NewRange(NewRangeMin, NewRangeMax); TimeSliderArgs.OnClampRangeChanged.ExecuteIfBound(NewRange); if( !TimeSliderArgs.ClampRange.IsBound() ) { // The output is not bound to a delegate so we'll manage the value ourselves (no animation) TimeSliderArgs.ClampRange.Set(NewRange); } } void FSequencerTimeSliderController::SetPlayRange( FFrameNumber RangeStart, int32 RangeDuration ) { check(RangeDuration >= 0); const TRange NewRange(RangeStart, RangeStart + RangeDuration); TimeSliderArgs.OnPlaybackRangeChanged.ExecuteIfBound(NewRange); if( !TimeSliderArgs.PlaybackRange.IsBound() ) { // The output is not bound to a delegate so we'll manage the value ourselves (no animation) TimeSliderArgs.PlaybackRange.Set(NewRange); } } void FSequencerTimeSliderController::SetSelectionRange(const TRange& NewRange) { TimeSliderArgs.OnSelectionRangeChanged.ExecuteIfBound(NewRange); } bool FSequencerTimeSliderController::ZoomByDelta( float InDelta, float MousePositionFraction ) { TRange LocalViewRange = GetViewRange().GetAnimationTarget(); double LocalViewRangeMax = LocalViewRange.GetUpperBoundValue(); double LocalViewRangeMin = LocalViewRange.GetLowerBoundValue(); const double OutputViewSize = LocalViewRangeMax - LocalViewRangeMin; const double OutputChange = OutputViewSize * InDelta; double NewViewOutputMin = LocalViewRangeMin - (OutputChange * MousePositionFraction); double NewViewOutputMax = LocalViewRangeMax + (OutputChange * (1.f - MousePositionFraction)); if( NewViewOutputMin < NewViewOutputMax ) { ClampViewRange(NewViewOutputMin, NewViewOutputMax); SetViewRange(NewViewOutputMin, NewViewOutputMax, EViewRangeInterpolation::Animated); return true; } return false; } void FSequencerTimeSliderController::PanByDelta( float InDelta ) { TRange LocalViewRange = GetViewRange().GetAnimationTarget(); double CurrentMin = LocalViewRange.GetLowerBoundValue(); double CurrentMax = LocalViewRange.GetUpperBoundValue(); // Adjust the delta to be a percentage of the current range InDelta *= ScrubConstants::ScrollPanFraction * (CurrentMax - CurrentMin); double NewViewOutputMin = CurrentMin + InDelta; double NewViewOutputMax = CurrentMax + InDelta; ClampViewRange(NewViewOutputMin, NewViewOutputMax); SetViewRange(NewViewOutputMin, NewViewOutputMax, EViewRangeInterpolation::Animated); } bool FSequencerTimeSliderController::HitTestRangeStart(const FScrubRangeToScreen& RangeToScreen, const TRange& Range, float HitPixel) const { static float BrushSizeInStateUnits = 6.f, DragToleranceSlateUnits = 2.f, MouseTolerance = 2.f; const float RangeStartPixel = RangeToScreen.InputToLocalX(Range.GetLowerBoundValue()); // Hit test against the brush region to the right of the playback start position, +/- DragToleranceSlateUnits return HitPixel >= RangeStartPixel - MouseTolerance - DragToleranceSlateUnits && HitPixel <= RangeStartPixel + MouseTolerance + BrushSizeInStateUnits + DragToleranceSlateUnits; } bool FSequencerTimeSliderController::HitTestRangeEnd(const FScrubRangeToScreen& RangeToScreen, const TRange& Range, float HitPixel) const { static float BrushSizeInStateUnits = 6.f, DragToleranceSlateUnits = 2.f, MouseTolerance = 2.f; const float RangeEndPixel = RangeToScreen.InputToLocalX(Range.GetUpperBoundValue()); // Hit test against the brush region to the left of the playback end position, +/- DragToleranceSlateUnits return HitPixel >= RangeEndPixel - MouseTolerance - BrushSizeInStateUnits - DragToleranceSlateUnits && HitPixel <= RangeEndPixel + MouseTolerance + DragToleranceSlateUnits; } bool FSequencerTimeSliderController::HitTestMark(const FGeometry& AllottedGeometry, const FScrubRangeToScreen& RangeToScreen, float HitPixel, bool bTestLabelBox, int32* OutMarkIndex, FFrameNumber* OutMarkFrameNumber) const { if (OutMarkIndex) { *OutMarkIndex = INDEX_NONE; } if (OutMarkFrameNumber) { *OutMarkFrameNumber = 0; } if (!WeakSequencer.Pin()->GetSequencerSettings()->GetShowMarkedFrames()) { return false; } const TArray & MarkedFrames = TimeSliderArgs.MarkedFrames.Get(); if (MarkedFrames.Num() < 1) { return false; } static float BrushSizeInStateUnits = 3.f, DragToleranceSlateUnits = 2.f, MouseTolerance = 2.f; for (int32 MarkIndex = 0; MarkIndex < MarkedFrames.Num(); ++MarkIndex) { double Seconds = MarkedFrames[MarkIndex].FrameNumber / GetTickResolution(); float MarkPixel = RangeToScreen.InputToLocalX(Seconds); // Hit test against the brush region to the left/right of the mark position, +/- DragToleranceSlateUnits if ((HitPixel >= MarkPixel - MouseTolerance - DragToleranceSlateUnits && HitPixel <= MarkPixel + MouseTolerance + BrushSizeInStateUnits + DragToleranceSlateUnits) || (HitPixel >= MarkPixel - MouseTolerance - BrushSizeInStateUnits - DragToleranceSlateUnits && HitPixel <= MarkPixel + MouseTolerance + DragToleranceSlateUnits)) { if (OutMarkIndex) { *OutMarkIndex = MarkIndex; } if (OutMarkFrameNumber) { *OutMarkFrameNumber = MarkedFrames[MarkIndex].FrameNumber; } return true; } // Hit test against the label box, which is the text size offset and grown by the margin. if (bTestLabelBox) { FVector2D TextPosition, TextSize; bool bDrawLeft; GetMarkLabelGeometry(AllottedGeometry, RangeToScreen, MarkedFrames[MarkIndex], TextPosition, TextSize, bDrawLeft); FVector2D TextBoxSize = TextSize + ScrubConstants::MarkLabelBoxMargin + ScrubConstants::MarkLabelBoxWideMargin; if (HitPixel >= MarkPixel && HitPixel <= MarkPixel + TextBoxSize.X) { if (OutMarkIndex) { *OutMarkIndex = MarkIndex; } if (OutMarkFrameNumber) { *OutMarkFrameNumber = MarkedFrames[MarkIndex].FrameNumber; } return true; } } } return false; } void FSequencerTimeSliderController::GetMarkLabelGeometry(const FGeometry& AllottedGeometry, const FScrubRangeToScreen& RangeToScreen, const FMovieSceneMarkedFrame& MarkedFrame, FVector2D& OutPosition, FVector2D& OutSize, bool& bIsDrawLeft) const { double Seconds = MarkedFrame.FrameNumber / GetTickResolution(); float MarkPixel = RangeToScreen.InputToLocalX(Seconds); const FString& LabelString = MarkedFrame.Label; FVector2D TextSize = FontMeasureService->Measure(LabelString, SmallLayoutFont); // Flip the text position if getting near the end of the view range bool bDrawLeft = (AllottedGeometry.Size.X - MarkPixel) < (TextSize.X + ScrubConstants::MarkLabelBoxMargin + ScrubConstants::MarkLabelBoxWideMargin); float TextPosition = bDrawLeft ? MarkPixel - TextSize.X - ScrubConstants::MarkLabelBoxMargin : MarkPixel + ScrubConstants::MarkLabelBoxMargin; OutPosition = FVector2D(TextPosition, 0); OutSize = TextSize; bIsDrawLeft = bDrawLeft; } FFrameTime FSequencerTimeSliderController::SnapTimeToNearestKey(const FPointerEvent& MouseEvent, const FScrubRangeToScreen& RangeToScreen, float CursorPos, FFrameTime InTime) const { using namespace UE::Sequencer; if (!WeakSequencer.IsValid()) { return InTime; } if (TimeSliderArgs.OnGetNearestKey.IsBound()) { ENearestKeyOption NearestKeyOption = ENearestKeyOption::NKO_None; if (WeakSequencer.Pin()->GetSequencerSettings()->GetSnapPlayTimeToKeys() || MouseEvent.IsShiftDown()) { EnumAddFlags(NearestKeyOption, ENearestKeyOption::NKO_SearchKeys); } if (WeakSequencer.Pin()->GetSequencerSettings()->GetSnapPlayTimeToSections() || MouseEvent.IsShiftDown()) { EnumAddFlags(NearestKeyOption, ENearestKeyOption::NKO_SearchSections); } if (WeakSequencer.Pin()->GetSequencerSettings()->GetSnapPlayTimeToMarkers() || MouseEvent.IsShiftDown()) { EnumAddFlags(NearestKeyOption, ENearestKeyOption::NKO_SearchMarkers); } FFrameNumber NearestKey = TimeSliderArgs.OnGetNearestKey.Execute(InTime, NearestKeyOption); float LocalKeyPos = RangeToScreen.InputToLocalX( NearestKey / GetTickResolution() ); static float MouseTolerance = 20.f; if (FMath::IsNearlyEqual(LocalKeyPos, CursorPos, MouseTolerance)) { return NearestKey; } } return InTime; } void FSequencerTimeSliderController::SetPlaybackRangeStart(FFrameNumber NewStart) { TRange PlaybackRange = TimeSliderArgs.PlaybackRange.Get(); NewStart = SnapSequencerTime(NewStart).FrameNumber; if (NewStart <= UE::MovieScene::DiscreteExclusiveUpper(PlaybackRange)) { TimeSliderArgs.OnPlaybackRangeChanged.ExecuteIfBound(TRange(NewStart, PlaybackRange.GetUpperBound())); } } void FSequencerTimeSliderController::SetPlaybackRangeEnd(FFrameNumber NewEnd) { TRange PlaybackRange = TimeSliderArgs.PlaybackRange.Get(); NewEnd = SnapSequencerTime(NewEnd).FrameNumber; if (NewEnd >= UE::MovieScene::DiscreteInclusiveLower(PlaybackRange)) { TimeSliderArgs.OnPlaybackRangeChanged.ExecuteIfBound(TRange(PlaybackRange.GetLowerBound(), TRangeBound::Exclusive(NewEnd))); } } void FSequencerTimeSliderController::SetSelectionRangeStart(FFrameNumber NewStart) { TRange SelectionRange = TimeSliderArgs.SelectionRange.Get(); NewStart = SnapSequencerTime(NewStart).FrameNumber; if (SelectionRange.IsEmpty()) { TimeSliderArgs.OnSelectionRangeChanged.ExecuteIfBound(TRange(NewStart, NewStart + 1)); } else if (NewStart <= UE::MovieScene::DiscreteExclusiveUpper(SelectionRange)) { TimeSliderArgs.OnSelectionRangeChanged.ExecuteIfBound(TRange(NewStart, SelectionRange.GetUpperBound())); } } void FSequencerTimeSliderController::SetSelectionRangeEnd(FFrameNumber NewEnd) { TRange SelectionRange = TimeSliderArgs.SelectionRange.Get(); NewEnd = SnapSequencerTime(NewEnd).FrameNumber; if (SelectionRange.IsEmpty()) { TimeSliderArgs.OnSelectionRangeChanged.ExecuteIfBound(TRange(NewEnd - 1, NewEnd)); } else if (NewEnd >= UE::MovieScene::DiscreteInclusiveLower(SelectionRange)) { TimeSliderArgs.OnSelectionRangeChanged.ExecuteIfBound(TRange(SelectionRange.GetLowerBound(), NewEnd)); } } void FSequencerTimeSliderController::HandleMarkSelection(int32 InMarkIndex) { using namespace UE::Sequencer; TSharedPtr Sequencer = WeakSequencer.Pin(); if (!Sequencer->GetSequencerSettings()->GetShowMarkedFrames()) { return; } FSequencerSelection& SequencerSelection = Sequencer->GetSelection(); FModifierKeysState ModifierKeys = FSlateApplication::Get().GetModifierKeys(); const bool bToggleSelection = ModifierKeys.AreModifersDown(EModifierKey::Control); const bool bAddToSelection = ModifierKeys.AreModifersDown(EModifierKey::Shift); if (!bToggleSelection && !bAddToSelection) { if (!SequencerSelection.MarkedFrames.IsSelected(InMarkIndex)) { SequencerSelection.Empty(); SequencerSelection.MarkedFrames.Select(InMarkIndex); } } else if (bAddToSelection || !SequencerSelection.MarkedFrames.IsSelected(InMarkIndex)) { SequencerSelection.MarkedFrames.Select(InMarkIndex); } else { SequencerSelection.MarkedFrames.Deselect(InMarkIndex); } } void FSequencerTimeSliderController::SetMark(FFrameNumber DiffFrame) { for (TMap::TConstIterator It = DragMarkMap.CreateConstIterator(); It; ++It) { int32 MarkIndex = It.Key(); FFrameNumber FrameNumber = SnapSequencerTime(It.Value() + DiffFrame).FrameNumber; TimeSliderArgs.OnSetMarkedFrame.ExecuteIfBound(MarkIndex, FrameNumber); } } void FSequencerTimeSliderController::AddMarkAtFrame(FFrameNumber FrameNumber) { TimeSliderArgs.OnAddMarkedFrame.ExecuteIfBound(FrameNumber); } void FSequencerTimeSliderController::DeleteMarkAtIndex(int32 InMarkIndex) { TimeSliderArgs.OnDeleteMarkedFrame.ExecuteIfBound(InMarkIndex); } void FSequencerTimeSliderController::DeleteAllMarks() { TimeSliderArgs.OnDeleteAllMarkedFrames.ExecuteIfBound(); } #undef LOCTEXT_NAMESPACE