// Copyright Epic Games, Inc. All Rights Reserved. #include "SequencerTimeSliderController.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 "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; } FSequencerTimeSliderController::FSequencerTimeSliderController( const FTimeSliderArgs& InArgs, TWeakPtr InWeakSequencer ) : WeakSequencer(InWeakSequencer) , TimeSliderArgs( InArgs ) , DistanceDragged( 0.0f ) , MouseDragType( DRAG_NONE ) , bMouseDownInRegion(false) , bPanning( false ) , DragMarkIndex( INDEX_NONE ) { ScrubFillBrush = FAppStyle::GetBrush( TEXT( "Sequencer.Timeline.ScrubFill" ) ); FrameBlockScrubHandleUpBrush = FAppStyle::GetBrush( TEXT( "Sequencer.Timeline.FrameBlockScrubHandleUp" ) ); FrameBlockScrubHandleDownBrush = FAppStyle::GetBrush( TEXT( "Sequencer.Timeline.FrameBlockScrubHandleDown" ) ); VanillaScrubHandleUpBrush = FAppStyle::GetBrush( TEXT( "Sequencer.Timeline.VanillaScrubHandleUp" ) ); VanillaScrubHandleDownBrush = FAppStyle::GetBrush( TEXT( "Sequencer.Timeline.VanillaScrubHandleDown" ) ); 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::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; } // Clamp first, snap to frame last if (Sequencer->GetSequencerSettings()->ShouldKeepCursorInPlayRangeWhileScrubbing()) { ScrubTime = UE::MovieScene::ClampToDiscreteRange(ScrubTime, TimeSliderArgs.PlaybackRange.Get()); } if ( Sequencer->GetSequencerSettings()->GetIsSnapEnabled() || MouseEvent.IsShiftDown() ) { if (Sequencer->GetSequencerSettings()->GetSnapPlayTimeToInterval()) { // Set the style of the scrub handle if (Sequencer->GetScrubStyle() == ESequencerScrubberStyle::FrameBlock) { // Floor to the display frame ScrubTime = ConvertFrameTime(ConvertFrameTime(ScrubTime, GetTickResolution(), GetDisplayRate()).FloorToFrame(), GetDisplayRate(), GetTickResolution()); } else { // Snap (round) to display rate ScrubTime = FFrameRate::Snap(ScrubTime, GetTickResolution(), GetDisplayRate()); } } // 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()->GetIsSnapEnabled()) { 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; } struct FSequencerTimeSliderController::FDrawTickArgs { /** Geometry of the area */ FGeometry AllottedGeometry; /** Culling rect of the area */ FSlateRect CullingRect; /** Color of each tick */ FLinearColor TickColor; /** Offset in Y where to start the tick */ float TickOffset; /** Height in of major ticks */ float MajorTickHeight; /** Start layer for elements */ int32 StartLayer; /** Draw effects to apply */ ESlateDrawEffect DrawEffects; /** Whether or not to only draw major ticks */ bool bOnlyDrawMajorTicks; /** Whether or not to mirror labels */ bool bMirrorLabels; }; void FSequencerTimeSliderController::DrawTicks( 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 SmallLayoutFont = 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); 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 = TimeSliderArgs.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( TextOffset, InArgs.AllottedGeometry.Size ), FrameString, SmallLayoutFont, 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::DrawMarkedFrames( const FGeometry& AllottedGeometry, const FScrubRangeToScreen& RangeToScreen, FSlateWindowElementList& OutDrawElements, int32 LayerId, const ESlateDrawEffect& DrawEffects, bool bDrawLabels ) const { const TArray & MarkedFrames = TimeSliderArgs.MarkedFrames.Get(); const TArray & GlobalMarkedFrames = TimeSliderArgs.GlobalMarkedFrames.Get(); if (MarkedFrames.Num() < 1 && GlobalMarkedFrames.Num() < 1) { return LayerId; } const TSharedRef< FSlateFontMeasure > FontMeasureService = FSlateApplication::Get().GetRenderer()->GetFontMeasureService(); FSlateFontInfo SmallLayoutFont = FCoreStyle::GetDefaultFontStyle("Regular", 10); FQualifiedFrameTime ScrubPosition = FQualifiedFrameTime(TimeSliderArgs.ScrubPosition.Get(), GetTickResolution()); FScrubberMetrics ScrubMetrics = GetScrubPixelMetrics(ScrubPosition, RangeToScreen); auto DrawFrameMarkers = ([=](const TArray & InMarkedFrames, FSlateWindowElementList& DrawElements, bool bFade) { for (const FMovieSceneMarkedFrame& MarkedFrame : InMarkedFrames) { double Seconds = MarkedFrame.FrameNumber / GetTickResolution(); FLinearColor DrawColor = bFade ? MarkedFrame.Color.Desaturate(0.25f) : MarkedFrame.Color; 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 ); FString LabelString = MarkedFrame.Label; if (bDrawLabels && !LabelString.IsEmpty()) { // Draw the label next to the marked frame line FVector2D TextSize = FontMeasureService->Measure(LabelString, 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 - LinePos) < (TextSize.X + 14.f) - TextOffsetPx; float TextPosition = bDrawLeft ? LinePos - TextSize.X - TextOffsetPx : LinePos + TextOffsetPx; FSlateDrawElement::MakeText( DrawElements, LayerId + 1, AllottedGeometry.ToPaintGeometry(FVector2D(TextPosition, 0.f), TextSize), LabelString, 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 { 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 const float MajorTickHeight = 9.0f; 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( FVector2D( HandleStart, 0 ), FVector2D( HandleEnd - HandleStart, AllottedGeometry.Size.Y ) ); FLinearColor ScrubColor = InWidgetStyle.GetColorAndOpacityTint(); if(bIsEvaluating) { // @todo Sequencer this color should be specified in the style ScrubColor.A = ScrubColor.A * 0.75f; ScrubColor.B *= 0.1f; ScrubColor.G *= 0.2f; } else { ScrubColor.A = ScrubColor.A * 0.75f; ScrubColor.R = 0.7f; ScrubColor.B = 0.1f; ScrubColor.G = 0.7f; } const FSlateBrush* Brush = ScrubMetrics.Style == ESequencerScrubberStyle::Vanilla ? ( bMirrorLabels ? VanillaScrubHandleUpBrush : VanillaScrubHandleDownBrush ) : ( bMirrorLabels ? FrameBlockScrubHandleUpBrush : FrameBlockScrubHandleDownBrush ); FSlateDrawElement::MakeBox( OutDrawElements, ArrowLayer, MyGeometry, Brush, DrawEffects, ScrubColor ); LayerId = DrawMarkedFrames(AllottedGeometry, RangeToScreen, OutDrawElements, LayerId, DrawEffects, 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 = TimeSliderArgs.NumericTypeInterface->ToString(TimeSliderArgs.ScrubPosition.Get().GetFrame().Value); } if (TimeSliderArgs.ScrubPositionParent.Get() != MovieSceneSequenceID::Invalid) { TextColor = FLinearColor::Yellow; } FSlateFontInfo SmallLayoutFont = FCoreStyle::GetDefaultFontStyle("Regular", 10); const TSharedRef< FSlateFontMeasure > FontMeasureService = FSlateApplication::Get().GetRenderer()->GetFontMeasureService(); FVector2D TextSize = FontMeasureService->Measure(FrameString, SmallLayoutFont); // Flip the text position if getting near the end of the view range 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( TextOffset, TextSize ), FrameString, SmallLayoutFont, Args.DrawEffects, TextColor ); } if (MouseDragType == DRAG_SETTING_RANGE) { FFrameRate Resolution = GetTickResolution(); FFrameTime MouseDownTime[2]; FScrubRangeToScreen MouseDownRange(GetViewRange(), MouseDownGeometry.Size); MouseDownTime[0] = ComputeFrameTimeFromMouse(MouseDownGeometry, MouseDownPosition[0], MouseDownRange); MouseDownTime[1] = ComputeFrameTimeFromMouse(MouseDownGeometry, MouseDownPosition[1], 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); FSlateDrawElement::MakeBox( OutDrawElements, LayerId+1, AllottedGeometry.ToPaintGeometry( FVector2D(RangePosX, 0.f), FVector2D(RangeSizeX, AllottedGeometry.Size.Y) ), bMirrorLabels ? VanillaScrubHandleDownBrush : VanillaScrubHandleUpBrush, 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(FVector2D(SelectionRangeL, 0.f), FVector2D(SelectionRangeR - SelectionRangeL, AllottedGeometry.Size.Y)), FAppStyle::GetBrush("WhiteBrush"), ESlateDrawEffect::None, DrawColor.CopyWithNewOpacity(Args.SolidFillOpacity) ); } FSlateDrawElement::MakeBox( OutDrawElements, LayerId + 1, AllottedGeometry.ToPaintGeometry(FVector2D(SelectionRangeL, 0.f), FVector2D(Args.BrushWidth, AllottedGeometry.Size.Y)), Args.StartBrush, ESlateDrawEffect::None, DrawColor ); FSlateDrawElement::MakeBox( OutDrawElements, LayerId + 1, AllottedGeometry.ToPaintGeometry(FVector2D(SelectionRangeR - Args.BrushWidth, 0.f), FVector2D(Args.BrushWidth, AllottedGeometry.Size.Y)), 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; FSlateDrawElement::MakeBox( OutDrawElements, LayerId+1, AllottedGeometry.ToPaintGeometry(FVector2D(PlaybackRangeL, 0.f), FVector2D(Args.BrushWidth, AllottedGeometry.Size.Y)), Args.StartBrush, ESlateDrawEffect::None, FColor(32, 128, 32, OpacityBlend) // 120, 75, 50 (HSV) ); FSlateDrawElement::MakeBox( OutDrawElements, LayerId+1, AllottedGeometry.ToPaintGeometry(FVector2D(PlaybackRangeR - Args.BrushWidth, 0.f), FVector2D(Args.BrushWidth, AllottedGeometry.Size.Y)), Args.EndBrush, ESlateDrawEffect::None, FColor(128, 32, 32, OpacityBlend) // 0, 75, 50 (HSV) ); // Black tint for excluded regions FSlateDrawElement::MakeBox( OutDrawElements, LayerId+1, AllottedGeometry.ToPaintGeometry(FVector2D(0.f, 0.f), FVector2D(PlaybackRangeL, AllottedGeometry.Size.Y)), FAppStyle::GetBrush("WhiteBrush"), ESlateDrawEffect::None, FLinearColor::Black.CopyWithNewOpacity(0.3f * OpacityBlend / 255.f) ); FSlateDrawElement::MakeBox( OutDrawElements, LayerId+1, AllottedGeometry.ToPaintGeometry(FVector2D(PlaybackRangeR, 0.f), FVector2D(AllottedGeometry.Size.X - PlaybackRangeR, AllottedGeometry.Size.Y)), 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")); FColor GreenTint(32, 128, 32); // 120, 75, 50 (HSV) FSlateDrawElement::MakeBox( OutDrawElements, LayerId+1, AllottedGeometry.ToPaintGeometry(FVector2D(SubSequenceRangeL, 0.f), FVector2D(Args.BrushWidth, AllottedGeometry.Size.Y)), LineBrushL, ESlateDrawEffect::None, GreenTint ); FColor RedTint(128, 32, 32); // 0, 75, 50 (HSV) FSlateDrawElement::MakeBox( OutDrawElements, LayerId+1, AllottedGeometry.ToPaintGeometry(FVector2D(SubSequenceRangeR - Args.BrushWidth, 0.f), FVector2D(Args.BrushWidth, AllottedGeometry.Size.Y)), LineBrushR, ESlateDrawEffect::None, RedTint ); // Black tint for excluded regions FSlateDrawElement::MakeBox( OutDrawElements, LayerId+1, AllottedGeometry.ToPaintGeometry(FVector2D(0.f, 0.f), FVector2D(SubSequenceRangeL, AllottedGeometry.Size.Y)), FAppStyle::GetBrush("WhiteBrush"), ESlateDrawEffect::None, FLinearColor::Black.CopyWithNewOpacity(0.3f) ); FSlateDrawElement::MakeBox( OutDrawElements, LayerId+1, AllottedGeometry.ToPaintGeometry(FVector2D(SubSequenceRangeR, 0.f), FVector2D(AllottedGeometry.Size.X - SubSequenceRangeR, AllottedGeometry.Size.Y)), 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(FVector2D(SubSequenceRangeL - 16.f, 0.f), FVector2D(16.f, AllottedGeometry.Size.Y)), FAppStyle::GetBrush("Sequencer.Timeline.SubSequenceRangeHashL"), ESlateDrawEffect::None, GreenTint ); FSlateDrawElement::MakeBox( OutDrawElements, LayerId+1, AllottedGeometry.ToPaintGeometry(FVector2D(SubSequenceRangeR, 0.f), FVector2D(16.f, AllottedGeometry.Size.Y)), FAppStyle::GetBrush("Sequencer.Timeline.SubSequenceRangeHashR"), ESlateDrawEffect::None, RedTint ); 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; DragMarkIndex = INDEX_NONE; 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; 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) { // Zooming FFrameTime MouseDownStart = ComputeFrameTimeFromMouse(MyGeometry, MouseDownPosition[0], 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) { TimeSliderArgs.OnEndScrubberMovement.ExecuteIfBound(); FFrameTime ScrubTime = MouseTime; FVector2D CursorPos = MouseEvent.GetScreenSpacePosition(); TSharedPtr Sequencer = WeakSequencer.Pin(); if (MouseDragType == DRAG_SCRUBBING_TIME) { ScrubTime = ComputeScrubTimeFromMouse(MyGeometry, MouseEvent, RangeToScreen); } else if (Sequencer.IsValid()) { ScrubTime = SnapTimeToNearestKey(MouseEvent, RangeToScreen, CursorPos.X, ScrubTime); } CommitScrubPosition( ScrubTime, /*bIsScrubbing=*/false , /*bEvaluate*/ !bHandleMiddleMouseButton); //if middle mouse button down we don't evaluate on the time change } MouseDragType = DRAG_NONE; DistanceDragged = 0.f; bMouseDownInRegion = false; return FReply::Handled().ReleaseMouseCapture(); } bMouseDownInRegion = false; return FReply::Unhandled(); } FReply FSequencerTimeSliderController::OnMouseMove( SWidget& WidgetOwner, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) { 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); if (bHandleRightMouseButton) { if (!bPanning) { DistanceDragged += FMath::Abs( MouseEvent.GetCursorDelta().X ); if ( DistanceDragged > 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) { TRange LocalViewRange = GetViewRange(); FScrubRangeToScreen RangeToScreen(LocalViewRange, MyGeometry.Size); DistanceDragged += FMath::Abs( MouseEvent.GetCursorDelta().X ); if ( MouseDragType == DRAG_NONE ) { if ( DistanceDragged > FSlateApplication::Get().GetDragTriggerDistance() ) { FFrameTime MouseDownFree = ComputeFrameTimeFromMouse(MyGeometry, MouseDownPosition[0], 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 (!bReadOnly && !bHitScrubber && HitTestMark(RangeToScreen, MouseDownPixel, DragMarkIndex) && bHandleMiddleMouseButton == false) { MouseDragType = DRAG_MARK; 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], 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(DragMarkIndex, MouseTime.FrameNumber); } 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(); } } } if ( DistanceDragged != 0.f && (bHandleLeftMouseButton || bHandleRightMouseButton) ) { return FReply::Handled().CaptureMouse(WidgetOwner.AsShared()); } return FReply::Handled(); } void FSequencerTimeSliderController::CommitScrubPosition( FFrameTime NewValue, bool bIsScrubbing, bool bEvaluate) { 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() && 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()->GetIsSnapEnabled() && Sequencer->GetSequencerSettings()->GetSnapPlayTimeToInterval()) { 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 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); } int32 DummyMarkIndex = INDEX_NONE; if (MouseDragType == DRAG_MARK || (!bReadOnly && !bHitScrubber && HitTestMark(RangeToScreen, HitTestPixel, DummyMarkIndex))) { 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 { 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, false); } LayerId = DrawVerticalFrames(AllottedGeometry, RangeToScreen, OutDrawElements, LayerId, DrawEffects); if( Args.bDisplayScrubPosition ) { FQualifiedFrameTime ScrubPosition = FQualifiedFrameTime(TimeSliderArgs.ScrubPosition.Get(), GetTickResolution()); FScrubberMetrics ScrubMetrics = GetScrubPixelMetrics(ScrubPosition, RangeToScreen); if (ScrubMetrics.bDrawExtents) { // Draw a box for the scrub position FSlateDrawElement::MakeBox( OutDrawElements, LayerId + 1, AllottedGeometry.ToPaintGeometry(FVector2D(ScrubMetrics.FrameExtentsPx.GetLowerBoundValue(), 0.0f), FVector2D(ScrubMetrics.FrameExtentsPx.Size(), AllottedGeometry.Size.Y)), ScrubFillBrush, DrawEffects, FLinearColor::White.CopyWithNewOpacity(0.5f) ); } // Draw a line for the scrub position TArray LinePoints; { float LinePos = RangeToScreen.InputToLocalX(ScrubPosition.AsSeconds()); 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(1.f, 1.f, 1.f, .5f), false ); } return LayerId; } TSharedRef FSequencerTimeSliderController::OpenSetPlaybackRangeMenu(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { TSharedPtr Sequencer = WeakSequencer.Pin(); const bool bReadOnly = Sequencer && Sequencer->IsReadOnly(); FScrubRangeToScreen RangeToScreen = FScrubRangeToScreen(GetViewRange(), MyGeometry.Size); const float MousePixel = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition()).X; FFrameNumber FrameNumber = ComputeFrameTimeFromMouse(MyGeometry, MouseEvent.GetScreenSpacePosition(), RangeToScreen).FrameNumber; const bool bShouldCloseWindowAfterMenuSelection = true; FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, nullptr); FText CurrentTimeText; CurrentTimeText = FText::FromString(TimeSliderArgs.NumericTypeInterface->ToString(FrameNumber.Value)); TRange PlaybackRange = TimeSliderArgs.PlaybackRange.Get(); MenuBuilder.BeginSection("SequencerPlaybackRangeMenu", FText::Format(LOCTEXT("PlaybackRangeTextFormat", "Playback Range ({0}):"), CurrentTimeText)); { MenuBuilder.AddMenuEntry( LOCTEXT("SetPlaybackStart", "Set Start Time"), FText(), FSlateIcon(), FUIAction( FExecuteAction::CreateLambda([=]{ SetPlaybackRangeStart(FrameNumber); }), FCanExecuteAction::CreateLambda([=]{ return !TimeSliderArgs.IsPlaybackRangeLocked.Get() && FrameNumber < UE::MovieScene::DiscreteExclusiveUpper(PlaybackRange); }) ) ); MenuBuilder.AddMenuEntry( LOCTEXT("SetPlaybackEnd", "Set End Time"), FText(), FSlateIcon(), FUIAction( FExecuteAction::CreateLambda([=]{ SetPlaybackRangeEnd(FrameNumber); }), FCanExecuteAction::CreateLambda([=]{ return !TimeSliderArgs.IsPlaybackRangeLocked.Get() && FrameNumber >= UE::MovieScene::DiscreteInclusiveLower(PlaybackRange); }) ) ); MenuBuilder.AddMenuEntry( LOCTEXT("ToggleLocked", "Locked"), LOCTEXT("ToggleLockedTooltip", "Lock/Unlock the playback range"), FSlateIcon(), FUIAction( FExecuteAction::CreateLambda([=] { TimeSliderArgs.OnTogglePlaybackRangeLocked.ExecuteIfBound(); }), FCanExecuteAction::CreateLambda([=]{ return !bReadOnly; }), FIsActionChecked::CreateLambda([=] { return TimeSliderArgs.IsPlaybackRangeLocked.Get(); }) ), NAME_None, EUserInterfaceActionType::ToggleButton ); } MenuBuilder.EndSection(); // SequencerPlaybackRangeMenu TRange SelectionRange = TimeSliderArgs.SelectionRange.Get(); MenuBuilder.BeginSection("SequencerSelectionRangeMenu", FText::Format(LOCTEXT("SelectionRangeTextFormat", "Selection Range ({0}):"), CurrentTimeText)); { MenuBuilder.AddMenuEntry( LOCTEXT("SetSelectionStart", "Set Selection Start"), FText(), FSlateIcon(), FUIAction( FExecuteAction::CreateLambda([=]{ SetSelectionRangeStart(FrameNumber); }), FCanExecuteAction::CreateLambda([=]{ return SelectionRange.IsEmpty() || FrameNumber < UE::MovieScene::DiscreteExclusiveUpper(SelectionRange); }) ) ); MenuBuilder.AddMenuEntry( LOCTEXT("SetSelectionEnd", "Set Selection End"), FText(), FSlateIcon(), FUIAction( FExecuteAction::CreateLambda([=]{ SetSelectionRangeEnd(FrameNumber); }), FCanExecuteAction::CreateLambda([=]{ return SelectionRange.IsEmpty() || FrameNumber >= UE::MovieScene::DiscreteInclusiveLower(SelectionRange); }) ) ); MenuBuilder.AddMenuEntry( LOCTEXT("ClearSelectionRange", "Clear Selection Range"), FText(), FSlateIcon(), FUIAction( FExecuteAction::CreateLambda([=]{ TimeSliderArgs.OnSelectionRangeChanged.ExecuteIfBound(TRange::Empty()); }), FCanExecuteAction::CreateLambda([=]{ return !SelectionRange.IsEmpty(); }) ) ); } MenuBuilder.EndSection(); // SequencerPlaybackRangeMenu UMovieSceneCompiledDataManager* CompiledDataManager = WeakSequencer.Pin()->GetEvaluationTemplate().GetCompiledDataManager(); const FMovieSceneSequenceHierarchy* Hierarchy = CompiledDataManager->FindHierarchy(WeakSequencer.Pin()->GetEvaluationTemplate().GetCompiledDataID()); if (TimeSliderArgs.ScrubPositionParentChain.IsSet() && Hierarchy) { MenuBuilder.BeginSection("SequencerParentChainMenu"); { TArray ParentChain = TimeSliderArgs.ScrubPositionParentChain.Get(); for (FMovieSceneSequenceID ParentID : ParentChain) { FText ParentText = WeakSequencer.Pin()->GetRootMovieSceneSequence()->GetDisplayName(); for (const TTuple& Pair : Hierarchy->AllSubSequenceData()) { if (Pair.Key == ParentID && Pair.Value.GetSequence()) { ParentText = Pair.Value.GetSequence()->GetDisplayName(); break; } } MenuBuilder.AddMenuEntry( ParentText, FText::Format(LOCTEXT("DisplayTimeSpace", "Display time in the space of {0}"), ParentText), FSlateIcon(), FUIAction( FExecuteAction::CreateLambda([=] { TimeSliderArgs.OnScrubPositionParentChanged.ExecuteIfBound(ParentID); }), FCanExecuteAction(), FIsActionChecked::CreateLambda([=] { return TimeSliderArgs.ScrubPositionParent.Get() == MovieSceneSequenceID::Invalid ? ParentID == TimeSliderArgs.ScrubPositionParentChain.Get().Last() : TimeSliderArgs.ScrubPositionParent.Get() == ParentID; }) ), NAME_None, EUserInterfaceActionType::RadioButton ); } } MenuBuilder.EndSection(); // Sequencer Parent Chain } MenuBuilder.BeginSection("SequencerMarkMenu", FText::Format(LOCTEXT("MarkTextFormat", "Mark ({0}):"), CurrentTimeText)); { FFrameNumber DisplayFrameNumber = GetDisplayRate().AsFrameNumber(FrameNumber / GetTickResolution()); UMovieScene* MovieScene = WeakSequencer.Pin()->GetFocusedMovieSceneSequence()->GetMovieScene(); bool bHasMarks = MovieScene->GetMarkedFrames().Num() > 0; int32 MarkedIndex = INDEX_NONE; HitTestMark(RangeToScreen, MousePixel, MarkedIndex); if (MarkedIndex != INDEX_NONE) { 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::CreateLambda([=]() { return MakeShared(WeakSequencer.Pin()->GetNumericTypeInterface()); })); DetailsView->SetStructureData(StructOnScope); ChildSlot [ DetailsView->GetWidget().ToSharedRef() ]; } virtual void NotifyPreChange( FProperty* PropertyAboutToChange ) override { MovieSceneToModify->Modify(); } virtual void NotifyPreChange( class FEditPropertyChain* PropertyAboutToChange ) override { MovieSceneToModify->Modify(); } }; TSharedRef Widget = SNew(SMarkedFramePropertyWidget, MovieScene, MarkedIndex, WeakSequencer); MenuBuilder.AddWidget(Widget, FText::GetEmpty(), false); } if (MarkedIndex == INDEX_NONE) { MenuBuilder.AddMenuEntry( LOCTEXT("AddMark", "Add Mark"), FText(), FSlateIcon(), FUIAction( FExecuteAction::CreateLambda( [=]{ AddMarkAtFrame(FrameNumber); }), FCanExecuteAction::CreateLambda([=]{ return !bReadOnly; })) ); } else { MenuBuilder.AddMenuEntry( LOCTEXT("DeleteMark", "Delete Mark"), FText(), FSlateIcon(), FUIAction( FExecuteAction::CreateLambda([=]{ DeleteMarkAtIndex(MarkedIndex); }), FCanExecuteAction::CreateLambda([=]{ return !bReadOnly; })) ); } MenuBuilder.AddMenuEntry( LOCTEXT("Delete All Marks", "Delete All Marks"), FText(), FSlateIcon(), FUIAction( FExecuteAction::CreateLambda([=]{ DeleteAllMarks(); }), FCanExecuteAction::CreateLambda([=]{ return !bReadOnly && bHasMarks; })) ); } MenuBuilder.EndSection(); // SequencerMarkMenu return MenuBuilder.MakeWidget(); } 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 FScrubRangeToScreen& RangeToScreen, float HitPixel, int32& OutMarkIndex) const { 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)) { OutMarkIndex = MarkIndex; return true; } } return false; } 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 there are any tracks selected we'll find the nearest key only on that track. If there are no keys selected, // we will try to find the nearest keys on all tracks. This mirrors the behavior of the Jump to Next Keyframe commands. const TSet< TWeakPtr >& SelectedNodes = WeakSequencer.Pin()->GetSelection().GetSelectedOutlinerItems(); if (SelectedNodes.Num() == 0) { EnumAddFlags(NearestKeyOption, ENearestKeyOption::NKO_SearchAllTracks); } 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(); if (NewStart <= UE::MovieScene::DiscreteExclusiveUpper(PlaybackRange)) { TimeSliderArgs.OnPlaybackRangeChanged.ExecuteIfBound(TRange(NewStart, PlaybackRange.GetUpperBound())); } } void FSequencerTimeSliderController::SetPlaybackRangeEnd(FFrameNumber NewEnd) { TRange PlaybackRange = TimeSliderArgs.PlaybackRange.Get(); 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(); 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(); 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::SetMark(int32 InMarkIndex, FFrameNumber FrameNumber) { TimeSliderArgs.OnSetMarkedFrame.ExecuteIfBound(InMarkIndex, 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