Jelajahi Sumber

新增时间轴标记

maboren 1 bulan lalu
induk
melakukan
04cbcc57ce

+ 546 - 0
Ability/Plugins/EzAbility/Source/EzAbilityEditor/Private/EZAbleTimeSliderController.cpp

@@ -0,0 +1,546 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+
+#include "EZAbleTimeSliderController.h"
+#include "Fonts/FontMeasure.h" 
+#include "Preferences/PersonaOptions.h"
+#include "Styling/AppStyle.h"
+#include "SEzAbilityTimeLine.h"
+
+struct FEZAbleSliderController::FDrawAbleTickArgs
+{
+	/** 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;
+
+};
+
+struct FEZAbleSliderController::FAbleScrubRangeToScreen
+{
+	double ViewStart;
+	float  PixelsPerInput;
+
+	FAbleScrubRangeToScreen(const TRange<double>& InViewInput, const FVector2D& InWidgetSize)
+	{
+		const double ViewInputRange = InViewInput.Size<double>();
+
+		ViewStart = InViewInput.GetLowerBoundValue();
+		PixelsPerInput = ViewInputRange > 0 ? static_cast<float>(InWidgetSize.X / ViewInputRange) : 0;
+	}
+
+	/** Local Widget Space -> Curve Input domain. */
+	double LocalXToInput(float ScreenX) const
+	{
+		return PixelsPerInput > 0 ? (ScreenX / PixelsPerInput) + ViewStart : ViewStart;
+	}
+
+	/** Curve Input domain -> local Widget Space */
+	float InputToLocalX(double Input) const
+	{
+		return static_cast<float>((Input - ViewStart) * PixelsPerInput);
+	}
+};
+
+
+FEZAbleSliderController::FEZAbleSliderController(const FTimeSliderArgs& InArgs, TWeakPtr<SEzAbilityTimeLine> InWeakTimeline, TSharedPtr<INumericTypeInterface<double>> InSecondaryNumericTypeInterface)
+	: WeakTimeline(InWeakTimeline)
+	, TimeSliderArgs( InArgs )
+	, SecondaryNumericTypeInterface(InSecondaryNumericTypeInterface)
+{
+	ScrubFillBrush = FAppStyle::GetBrush(TEXT("Sequencer.Timeline.ScrubFill"));
+	ScrubHandleUpBrush = FAppStyle::GetBrush(TEXT("Sequencer.Timeline.VanillaScrubHandleUp"));
+	ScrubHandleDownBrush = FAppStyle::GetBrush(TEXT("Sequencer.Timeline.VanillaScrubHandleDown"));
+	EditableTimeBrush = FAppStyle::GetBrush(TEXT("AnimTimeline.SectionMarker"));
+}
+
+void FEZAbleSliderController::DrawTicks(FSlateWindowElementList& OutDrawElements, const TRange<double>& ViewRange, const FAbleScrubRangeToScreen& RangeToScreen, FDrawAbleTickArgs& InArgs) const
+{
+	TSharedPtr<SEzAbilityTimeLine> Timeline = WeakTimeline.Pin();
+	if (!Timeline.IsValid())
+	{
+		return;
+	}
+
+	if (!FMath::IsFinite(ViewRange.GetLowerBoundValue()) || !FMath::IsFinite(ViewRange.GetUpperBoundValue()))
+	{
+		return;
+	}
+
+	FFrameRate     FrameResolution = GetTickResolution();
+	FPaintGeometry PaintGeometry = InArgs.AllottedGeometry.ToPaintGeometry();
+	FSlateFontInfo SmallLayoutFont = FCoreStyle::GetDefaultFontStyle("Regular", 8);
+
+	double MajorGridStep = 0.0;
+	int32  MinorDivisions = 0;
+// 	if (!Timeline->GetGridMetrics(static_cast<float>(InArgs.AllottedGeometry.Size.X), MajorGridStep, MinorDivisions))
+// 	{
+// 		return;
+// 	}
+
+	if (InArgs.bOnlyDrawMajorTicks)
+	{
+		MinorDivisions = 0;
+	}
+
+	TArray<FVector2D> 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;
+
+	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)
+		{
+			FString FrameString = TimeSliderArgs.NumericTypeInterface->ToString((CurrentMajorLine * FrameResolution).RoundToFrame().Value);
+
+			// Space the text between the tick mark but slightly above
+			FVector2D TextOffset(MajorLinePx + 5.f, InArgs.bMirrorLabels ? 3.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,
+				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 FEZAbleSliderController::DrawSelectionRange(const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FAbleScrubRangeToScreen& RangeToScreen, const FPaintPlaybackRangeArgs& Args) const
+{
+	TRange<double> SelectionRange = TimeSliderArgs.SelectionRange.Get() / GetTickResolution();
+
+	if (!SelectionRange.IsEmpty() && SelectionRange.HasLowerBound() && SelectionRange.HasUpperBound())
+	{
+		const float SelectionRangeL = RangeToScreen.InputToLocalX(SelectionRange.GetLowerBoundValue()) - 1;
+		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(FVector2D(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(FVector2D(SelectionRangeL, 0.f))),
+			Args.StartBrush,
+			ESlateDrawEffect::None,
+			DrawColor
+		);
+
+		FSlateDrawElement::MakeBox(
+			OutDrawElements,
+			LayerId + 1,
+			AllottedGeometry.ToPaintGeometry(FVector2f(Args.BrushWidth, AllottedGeometry.Size.Y), FSlateLayoutTransform(FVector2D(SelectionRangeR - Args.BrushWidth, 0.f))),
+			Args.EndBrush,
+			ESlateDrawEffect::None,
+			DrawColor
+		);
+	}
+
+	return LayerId + 1;
+}
+
+int32 FEZAbleSliderController::DrawPlaybackRange(const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FAbleScrubRangeToScreen& RangeToScreen, const FPaintPlaybackRangeArgs& Args) const
+{
+	if (!TimeSliderArgs.PlaybackRange.IsSet())
+	{
+		return LayerId;
+	}
+
+	const uint8 OpacityBlend = TimeSliderArgs.SubSequenceRange.Get().IsSet() ? 128 : 255;
+
+	TRange<FFrameNumber> 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(FVector2f(Args.BrushWidth, AllottedGeometry.Size.Y), FSlateLayoutTransform(FVector2D(PlaybackRangeL, 0.f))),
+		Args.StartBrush,
+		ESlateDrawEffect::None,
+		FColor(32, 128, 32, OpacityBlend)	// 120, 75, 50 (HSV)
+	);
+
+	FSlateDrawElement::MakeBox(
+		OutDrawElements,
+		LayerId + 1,
+		AllottedGeometry.ToPaintGeometry(FVector2f(Args.BrushWidth, AllottedGeometry.Size.Y), FSlateLayoutTransform(FVector2D(PlaybackRangeR - Args.BrushWidth, 0.f))),
+		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(FVector2f(PlaybackRangeL, AllottedGeometry.Size.Y), FSlateLayoutTransform(FVector2D(0.f, 0.f))),
+		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(FVector2D(PlaybackRangeR, 0.f))),
+		FAppStyle::GetBrush("WhiteBrush"),
+		ESlateDrawEffect::None,
+		FLinearColor::Black.CopyWithNewOpacity(0.3f * OpacityBlend / 255.f)
+	);
+
+	return LayerId + 1;
+}
+
+int32 FEZAbleSliderController::OnPaintTimeSlider(bool bMirrorLabels, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
+{
+	const bool bEnabled = bParentEnabled;
+	const ESlateDrawEffect DrawEffects = bEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect;
+
+	const TRange<double> LocalViewRange = TimeSliderArgs.ViewRange.Get();
+	const float    LocalViewRangeMin = static_cast<float>(LocalViewRange.GetLowerBoundValue());
+	const float    LocalViewRangeMax = static_cast<float>(LocalViewRange.GetUpperBoundValue());
+	const float    LocalSequenceLength = LocalViewRangeMax - LocalViewRangeMin;
+	const TRange<FFrameNumber> LocalPlaybackRange = TimeSliderArgs.PlaybackRange.Get();
+
+	if (LocalSequenceLength > 0)
+	{
+		FAbleScrubRangeToScreen RangeToScreen(LocalViewRange, AllottedGeometry.Size);
+
+		// draw tick marks
+		constexpr float MajorTickHeight = 9.0f;
+
+		FDrawAbleTickArgs 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(static_cast<float>(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);
+
+		PlaybackRangeArgs.SolidFillOpacity = 0.05f;
+		LayerId = DrawSelectionRange(AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, RangeToScreen, PlaybackRangeArgs);
+
+		LayerId = DrawEditableTimes(AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, RangeToScreen);
+
+		// Draw the scrub handle
+		const float      HandleStart = RangeToScreen.InputToLocalX(TimeSliderArgs.ScrubPosition.Get().AsDecimal() / GetTickResolution().AsDecimal()) - 7.0f;
+		const float      HandleEnd = HandleStart + 13.0f;
+
+		const int32 ArrowLayer = LayerId + 2;
+		FPaintGeometry MyGeometry = AllottedGeometry.ToPaintGeometry(FVector2f(HandleEnd - HandleStart, AllottedGeometry.Size.Y), FSlateLayoutTransform(FVector2f(HandleStart, 0.f)));
+		FLinearColor ScrubColor = InWidgetStyle.GetColorAndOpacityTint();
+		{
+			ScrubColor.A = ScrubColor.A * 0.75f;
+			ScrubColor.B *= 0.1f;
+			ScrubColor.G *= 0.2f;
+		}
+
+		const FSlateBrush* Brush = (bMirrorLabels ? ScrubHandleUpBrush : ScrubHandleDownBrush);
+
+		FSlateDrawElement::MakeBox(
+			OutDrawElements,
+			ArrowLayer,
+			MyGeometry,
+			Brush,
+			DrawEffects,
+			ScrubColor
+		);
+
+		{
+			// Draw the current time next to the scrub handle
+			FString FrameString = TimeSliderArgs.NumericTypeInterface->ToString(TimeSliderArgs.ScrubPosition.Get().GetFrame().Value);
+
+			if (GetDefault<UPersonaOptions>()->bTimelineDisplayFormatSecondary)
+			{
+				// @TODO: need another numeric type interface??
+				FString SecondaryString = SecondaryNumericTypeInterface->ToString(TimeSliderArgs.ScrubPosition.Get().GetFrame().Value);
+				FrameString += TEXT(" (") + SecondaryString + TEXT(")");
+			}
+
+			if (GetDefault<UPersonaOptions>()->bTimelineDisplayPercentage)
+			{
+				double Percentage = FMath::Clamp(TimeSliderArgs.ScrubPosition.Get().AsDecimal() / FFrameTime(LocalPlaybackRange.Size<FFrameNumber>()).AsDecimal(), 0.0, 1.0);
+				FNumberFormattingOptions Options;
+				Options.MaximumFractionalDigits = 2;
+				FrameString += TEXT(" (") + FText::AsPercent(Percentage, &Options).ToString() + TEXT(")");
+			}
+
+			FSlateFontInfo SmallLayoutFont = FCoreStyle::GetDefaultFontStyle("Regular", 10);
+
+			const TSharedRef< FSlateFontMeasure > FontMeasureService = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
+			const FVector2D TextSize = FontMeasureService->Measure(FrameString, SmallLayoutFont);
+
+			// Flip the text position if getting near the end of the view range
+			constexpr float TextOffsetPx = 2.f;
+			const bool  bDrawLeft = (static_cast<float>(AllottedGeometry.Size.X) - HandleEnd) < (TextSize.X + 14.f) - TextOffsetPx;
+			const float TextPosition = bDrawLeft ? HandleStart - static_cast<float>(TextSize.X) - TextOffsetPx : HandleEnd + TextOffsetPx;
+
+			FVector2D TextOffset(TextPosition, Args.bMirrorLabels ? TextSize.Y - 6.f : Args.AllottedGeometry.Size.Y - (Args.MajorTickHeight + TextSize.Y));
+
+			FSlateDrawElement::MakeText(
+				OutDrawElements,
+				Args.StartLayer + 1,
+				Args.AllottedGeometry.ToPaintGeometry(TextSize, FSlateLayoutTransform(TextOffset)),
+				FrameString,
+				SmallLayoutFont,
+				Args.DrawEffects,
+				Args.TickColor
+			);
+		}
+
+// 		if (MouseDragType == DRAG_SETTING_RANGE)
+// 		{
+// 			FFrameRate Resolution = GetTickResolution();
+// 			FFrameTime MouseDownTime[2];
+// 
+// 			FScrubRangeToScreen MouseDownRange(TimeSliderArgs.ViewRange.Get(), 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(FVector2f(RangeSizeX, AllottedGeometry.Size.Y), FSlateLayoutTransform(FVector2f(RangePosX, 0.f))),
+// 				bMirrorLabels ? ScrubHandleDownBrush : ScrubHandleUpBrush,
+// 				DrawEffects,
+// 				MouseStartPosX < MouseEndPosX ? FLinearColor(0.5f, 0.5f, 0.5f) : FLinearColor(0.25f, 0.3f, 0.3f)
+// 			);
+// 		}
+
+		return ArrowLayer;
+	}
+
+	return LayerId;
+}
+
+int32 FEZAbleSliderController::OnPaintViewArea(const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, bool bEnabled, const FPaintViewAreaArgs& Args) const
+{
+	const ESlateDrawEffect DrawEffects = bEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect;
+
+	TRange<double> LocalViewRange = TimeSliderArgs.ViewRange.Get();
+	FAbleScrubRangeToScreen RangeToScreen(LocalViewRange, AllottedGeometry.Size);
+
+	if (Args.PlaybackRangeArgs.IsSet())
+	{
+		FPaintPlaybackRangeArgs PaintArgs = Args.PlaybackRangeArgs.GetValue();
+		LayerId = DrawPlaybackRange(AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, RangeToScreen, PaintArgs);
+		PaintArgs.SolidFillOpacity = 0.2f;
+		LayerId = DrawSelectionRange(AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, RangeToScreen, PaintArgs);
+	}
+
+	if (Args.bDisplayTickLines)
+	{
+		static FLinearColor TickColor(0.1f, 0.1f, 0.1f, 0.3f);
+
+		// Draw major tick lines in the section area
+		FDrawAbleTickArgs 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 = static_cast<float>(AllottedGeometry.Size.Y);
+		}
+
+		DrawTicks(OutDrawElements, LocalViewRange, RangeToScreen, DrawTickArgs);
+	}
+
+	if (Args.bDisplayScrubPosition)
+	{
+		// Draw a line for the scrub position
+		const float LinePos = RangeToScreen.InputToLocalX(TimeSliderArgs.ScrubPosition.Get().AsDecimal() / GetTickResolution().AsDecimal());
+
+		TArray<FVector2D> LinePoints;
+		{
+			LinePoints.AddUninitialized(2);
+			LinePoints[0] = FVector2D(0.0f, 0.0f);
+			LinePoints[1] = FVector2D(0.0f, FMath::FloorToFloat(AllottedGeometry.Size.Y));
+		}
+
+		FSlateDrawElement::MakeLines(
+			OutDrawElements,
+			LayerId + 1,
+			AllottedGeometry.ToPaintGeometry(FVector2f(1.0f, 1.0f), FSlateLayoutTransform(FVector2f(LinePos, 0.0f))),
+			LinePoints,
+			DrawEffects,
+			FLinearColor(1.f, 1.f, 1.f, .5f),
+			false
+		);
+	}
+
+	//TSharedPtr<FAnimModel> AnimModel = WeakModel.Pin();
+// 	if (AnimModel.IsValid())
+// 	{
+		const FLinearColor LineColor = GetDefault<UPersonaOptions>()->SectionTimingNodeColor;
+
+		// Draw all the times that we can drag in the timeline
+		TArray<double> EditableTimes = {0,1,2,3,4,5,6,7,8,9,10};
+
+		for (double Time : EditableTimes)
+		{
+			const float LinePos = RangeToScreen.InputToLocalX(Time);
+
+			TArray<FVector2D> LinePoints;
+			{
+				LinePoints.AddUninitialized(2);
+				LinePoints[0] = FVector2D(0.0f, 0.0f);
+				LinePoints[1] = FVector2D(0.0f, FMath::FloorToFloat(AllottedGeometry.Size.Y));
+			}
+
+			FSlateDrawElement::MakeLines(
+				OutDrawElements,
+				LayerId + 1,
+				AllottedGeometry.ToPaintGeometry(FVector2f(1.0f, 1.0f), FSlateLayoutTransform(FVector2f(LinePos, 0.0f))),
+				LinePoints,
+				DrawEffects,
+				LineColor,
+				false
+			);
+		}
+//	}
+
+	return LayerId;
+}
+
+FReply FEZAbleSliderController::OnMouseButtonDown(SWidget& WidgetOwner, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
+{
+	return FReply::Handled();
+}
+
+FReply FEZAbleSliderController::OnMouseButtonUp(SWidget& WidgetOwner, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
+{
+	return FReply::Handled();
+}
+
+FReply FEZAbleSliderController::OnMouseMove(SWidget& WidgetOwner, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
+{
+	return FReply::Handled();
+}
+
+FReply FEZAbleSliderController::OnMouseWheel(SWidget& WidgetOwner, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
+{
+	return FReply::Handled();
+}
+
+int32 FEZAbleSliderController::DrawEditableTimes(const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FAbleScrubRangeToScreen& RangeToScreen) const
+{
+	const FLinearColor TimeColor(255,255,255,0.5); //=  GetDefault<UPersonaOptions>()->SectionTimingNodeColor;
+
+	// Draw all the times that we can drag in the timeline
+	TArray<double> EditableTimes = {0};
+	for (double Time : EditableTimes) // WeakModel.Pin()->GetEditableTimes()
+	{
+		const float LinePos = RangeToScreen.InputToLocalX(Time);
+
+		FSlateDrawElement::MakeBox(
+			OutDrawElements,
+			LayerId + 1,
+			AllottedGeometry.ToPaintGeometry(FVector2f(11.0f, 12.0f), FSlateLayoutTransform(FVector2f(LinePos - 6.0f, AllottedGeometry.Size.Y - 12.0f))),
+			EditableTimeBrush,
+			ESlateDrawEffect::None,
+			TimeColor
+		);
+	}
+
+	return LayerId + 1;
+}
+
+

+ 3 - 2
Ability/Plugins/EzAbility/Source/EzAbilityEditor/Private/EzAbilityTimelineSummoner.cpp

@@ -9,6 +9,7 @@
 #include "SAdvancedPreviewDetailsTab.h"
 #include "SEditorViewport.h"
 #include "EzAbilityTimelinePanel.h"
+#include "SEzAbilityTimeLine.h"
 
 #define LOCTEXT_NAMESPACE "AbilityModes"
 
@@ -49,8 +50,8 @@ FEzAbilityTimelineSummoner::FEzAbilityTimelineSummoner(TSharedPtr<class FAssetEd
 
 TSharedRef<SWidget> FEzAbilityTimelineSummoner::CreateTabBody(const FWorkflowTabSpawnInfo& Info) const
 {
-
-	return SNew(SEzAbilityTimelinePanel);
+	
+	return SNew(SEzAbilityTimeLine);
 	// 	return SNew(SButton)
 	// 		.VAlign(VAlign_Center)
 	// 		.HAlign(HAlign_Center)

+ 65 - 0
Ability/Plugins/EzAbility/Source/EzAbilityEditor/Private/SAbleSequencerTimeSlider.cpp

@@ -0,0 +1,65 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "SAbleSequencerTimeSlider.h"
+
+#include "Math/UnrealMathSSE.h"
+#include "Widgets/SCompoundWidget.h"
+
+class FSlateRect;
+class FWidgetStyle;
+struct FGeometry;
+struct FPointerEvent;
+
+#define LOCTEXT_NAMESPACE "STimeSlider"
+
+
+void SAbleSequencerTimeSlider::Construct( const SAbleSequencerTimeSlider::FArguments& InArgs, TSharedRef<ITimeSliderController> InTimeSliderController )
+{
+	TimeSliderController = InTimeSliderController;
+	bMirrorLabels = InArgs._MirrorLabels;
+}
+
+int32 SAbleSequencerTimeSlider::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const
+{
+	int32 NewLayer = TimeSliderController->OnPaintTimeSlider( bMirrorLabels, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled );
+
+	return FMath::Max( NewLayer, SCompoundWidget::OnPaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, NewLayer, InWidgetStyle, ShouldBeEnabled( bParentEnabled ) ) );
+}
+
+FReply SAbleSequencerTimeSlider::OnMouseButtonDown( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
+{
+	TimeSliderController->OnTimeSliderMouseButtonDown( *this, MyGeometry, MouseEvent );
+	return FReply::Handled().CaptureMouse(AsShared()).PreventThrottling();
+}
+
+FReply SAbleSequencerTimeSlider::OnMouseButtonUp( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
+{
+	return TimeSliderController->OnTimeSliderMouseButtonUp( *this,  MyGeometry, MouseEvent );
+}
+
+FReply SAbleSequencerTimeSlider::OnMouseMove( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
+{
+	return TimeSliderController->OnTimeSliderMouseMove( *this, MyGeometry, MouseEvent );
+}
+
+FReply SAbleSequencerTimeSlider::OnMouseButtonDoubleClick(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
+{
+	return TimeSliderController->OnMouseButtonDoubleClick( SharedThis(this), MyGeometry, MouseEvent );
+}
+
+FVector2D SAbleSequencerTimeSlider::ComputeDesiredSize( float ) const
+{
+	return FVector2D(100, 22);
+}
+
+FReply SAbleSequencerTimeSlider::OnMouseWheel( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
+{
+	return TimeSliderController->OnTimeSliderMouseWheel( *this, MyGeometry, MouseEvent );
+}
+
+FCursorReply SAbleSequencerTimeSlider::OnCursorQuery(const FGeometry& MyGeometry, const FPointerEvent& CursorEvent) const
+{
+	return TimeSliderController->OnCursorQuery( SharedThis(this), MyGeometry, CursorEvent );
+}
+
+#undef LOCTEXT_NAMESPACE

+ 489 - 0
Ability/Plugins/EzAbility/Source/EzAbilityEditor/Private/SAbleTimeRange.cpp

@@ -0,0 +1,489 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "SAbleTimeRange.h"
+
+#include "AnimatedRange.h"
+#include "HAL/Platform.h"
+#include "HAL/PlatformCrt.h"
+#include "Internationalization/Internationalization.h"
+#include "Layout/Children.h"
+#include "Layout/Margin.h"
+#include "Layout/Visibility.h"
+#include "Math/Color.h"
+#include "Math/Range.h"
+#include "Misc/Attribute.h"
+#include "Misc/FrameNumber.h"
+#include "Misc/FrameRate.h"
+#include "Misc/FrameTime.h"
+#include "Misc/Optional.h"
+#include "MovieSceneTimeHelpers.h"
+#include "SlotBase.h"
+#include "Styling/AppStyle.h"
+#include "Styling/ISlateStyle.h"
+#include "Styling/SlateColor.h"
+#include "Styling/SlateTypes.h"
+#include "Types/SlateStructs.h"
+#include "Widgets/Input/SSpinBox.h"
+#include "Widgets/Layout/SBorder.h"
+#include "Widgets/Layout/SBox.h"
+#include "Widgets/SBoxPanel.h"
+#include "Widgets/SCompoundWidget.h"
+#include "Widgets/SNullWidget.h"
+
+class SWidget;
+template <typename NumericType> struct INumericTypeInterface;
+
+#define LOCTEXT_NAMESPACE "STimeRange"
+
+void SAbleTimeRange::Construct( const SAbleTimeRange::FArguments& InArgs, TSharedRef<ITimeSliderController> InTimeSliderController, TSharedRef<INumericTypeInterface<double>> NumericTypeInterface )
+{
+	TimeSliderController = InTimeSliderController;
+
+	TSharedRef<SWidget> WorkingRangeStart = SNullWidget::NullWidget, WorkingRangeEnd = SNullWidget::NullWidget;
+	if (InArgs._ShowWorkingRange)
+	{
+		WorkingRangeStart = SNew(SSpinBox<double>)
+		.IsEnabled(InArgs._EnableWorkingRange)
+		.Value(this, &SAbleTimeRange::WorkingStartTime)
+		.ToolTipText(LOCTEXT("WorkingRangeStart", "Working Range Start"))
+// 		.OnValueCommitted(this, &SAbleTimeRange::OnWorkingStartTimeCommitted)
+// 		.OnValueChanged(this, &SAbleTimeRange::OnWorkingStartTimeChanged)
+		.MinValue(TOptional<double>())
+		.MaxValue(TOptional<double>())
+		.Style(&FAppStyle::Get().GetWidgetStyle<FSpinBoxStyle>("Sequencer.HyperlinkSpinBox"))
+		.TypeInterface(NumericTypeInterface)
+		.ClearKeyboardFocusOnCommit(true)
+		.Delta(this, &SAbleTimeRange::GetSpinboxDelta)
+		.LinearDeltaSensitivity(25);
+
+		WorkingRangeEnd = SNew(SSpinBox<double>)
+		.IsEnabled(InArgs._EnableWorkingRange)
+		.Value(this, &SAbleTimeRange::WorkingEndTime)
+		.ToolTipText(LOCTEXT("WorkingRangeEnd", "Working Range End"))
+// 		.OnValueCommitted( this, &SAbleTimeRange::OnWorkingEndTimeCommitted )
+// 		.OnValueChanged( this, &SAbleTimeRange::OnWorkingEndTimeChanged )
+		.MinValue(TOptional<double>())
+		.MaxValue(TOptional<double>())
+		.Style(&FAppStyle::Get().GetWidgetStyle<FSpinBoxStyle>("Sequencer.HyperlinkSpinBox"))
+		.TypeInterface(NumericTypeInterface)
+		.ClearKeyboardFocusOnCommit(true)
+//		.Delta(this, &SAbleTimeRange::GetSpinboxDelta)
+		.LinearDeltaSensitivity(25);
+	}
+
+// 	TSharedRef<SWidget> ViewRangeStart = SNullWidget::NullWidget, ViewRangeEnd = SNullWidget::NullWidget;
+// 	if (InArgs._ShowViewRange)
+// 	{
+// 		ViewRangeStart = SNew(SSpinBox<double>)
+// 		.IsEnabled(InArgs._EnableViewRange)
+// 		.Value(this, &SAbleTimeRange::ViewStartTime)
+// 		.ToolTipText(LOCTEXT("ViewStartTimeTooltip", "View Range Start Time"))
+// 		.OnValueCommitted( this, &SAbleTimeRange::OnViewStartTimeCommitted )
+// 		.OnValueChanged( this, &SAbleTimeRange::OnViewStartTimeChanged )
+// 		.MinValue(TOptional<double>())
+// 		.MaxValue(TOptional<double>())
+// 		.Style(&FAppStyle::Get().GetWidgetStyle<FSpinBoxStyle>("Sequencer.HyperlinkSpinBox"))
+// 		.TypeInterface(NumericTypeInterface)
+// 		.ClearKeyboardFocusOnCommit(true)
+// 		.Delta(this, &SAbleTimeRange::GetSpinboxDelta)
+// 		.LinearDeltaSensitivity(25);
+// 
+// 
+// 		ViewRangeEnd = SNew(SSpinBox<double>)
+// 		.IsEnabled(InArgs._EnableViewRange)
+// 		.Value(this, &SAbleTimeRange::ViewEndTime)
+// 		.ToolTipText(LOCTEXT("ViewEndTimeTooltip", "View Range End Time"))
+// 		.OnValueCommitted( this, &SAbleTimeRange::OnViewEndTimeCommitted )
+// 		.OnValueChanged( this, &SAbleTimeRange::OnViewEndTimeChanged )
+// 		.MinValue(TOptional<double>())
+// 		.MaxValue(TOptional<double>())
+// 		.Style(&FAppStyle::Get().GetWidgetStyle<FSpinBoxStyle>("Sequencer.HyperlinkSpinBox"))
+// 		.TypeInterface(NumericTypeInterface)
+// 		.ClearKeyboardFocusOnCommit(true)
+// 		.Delta(this, &SAbleTimeRange::GetSpinboxDelta)
+// 		.LinearDeltaSensitivity(25);
+// 	}
+// 
+// 	TSharedRef<SWidget> PlaybackRangeStart = SNullWidget::NullWidget, PlaybackRangeEnd = SNullWidget::NullWidget;
+// 	if (InArgs._ShowPlaybackRange)
+// 	{
+// 		PlaybackRangeStart = SNew(SSpinBox<double>)
+// 		.IsEnabled(InArgs._EnablePlaybackRange)
+// 		.Value(this, &SAbleTimeRange::PlayStartTime)
+// 		.ToolTipText(LOCTEXT("PlayStartTimeTooltip", "Playback Range Start Time"))
+// 		.OnValueCommitted(this, &SAbleTimeRange::OnPlayStartTimeCommitted)
+// 		.OnValueChanged(this, &SAbleTimeRange::OnPlayStartTimeChanged)
+// 		.MinValue(TOptional<double>())
+// 		.MaxValue(TOptional<double>())
+// 		.Style(&FAppStyle::Get().GetWidgetStyle<FSpinBoxStyle>("Sequencer.HyperlinkSpinBox"))
+// 		.TypeInterface(NumericTypeInterface)
+// 		.ClearKeyboardFocusOnCommit(true)
+// 		.Delta(this, &SAbleTimeRange::GetSpinboxDelta)
+// 		.LinearDeltaSensitivity(25);
+// 
+// 
+// 		PlaybackRangeEnd = SNew(SSpinBox<double>)
+// 		.IsEnabled(InArgs._EnablePlaybackRange)
+// 		.Value(this, &SAbleTimeRange::PlayEndTime)
+// 		.ToolTipText(LOCTEXT("PlayEndTimeTooltip", "Playback Range Stop Time"))
+// 		.OnValueCommitted( this, &SAbleTimeRange::OnPlayEndTimeCommitted )
+// 		.OnValueChanged( this, &SAbleTimeRange::OnPlayEndTimeChanged )
+// 		.MinValue(TOptional<double>())
+// 		.MaxValue(TOptional<double>())
+// 		.Style(&FAppStyle::Get().GetWidgetStyle<FSpinBoxStyle>("Sequencer.HyperlinkSpinBox"))
+// 		.TypeInterface(NumericTypeInterface)
+// 		.ClearKeyboardFocusOnCommit(true)
+// 		.Delta(this, &SAbleTimeRange::GetSpinboxDelta)
+// 		.LinearDeltaSensitivity(25);
+// 	}
+
+	this->ChildSlot
+	.HAlign(HAlign_Fill)
+	[
+		SNew(SHorizontalBox)
+
+		+SHorizontalBox::Slot()
+		.VAlign(VAlign_Center)
+		.AutoWidth()
+		.Padding(2.f)
+		[
+			SNew(SBox)
+			.Visibility(InArgs._ShowWorkingRange ? EVisibility::Visible : EVisibility::Collapsed)
+			.MinDesiredWidth(64.f)
+			.HAlign(HAlign_Center)
+			[
+				WorkingRangeStart
+			]
+		]
+
+		+SHorizontalBox::Slot()
+		.VAlign(VAlign_Center)
+		.AutoWidth()
+		.Padding(2.f)
+		[
+			SNew(SBox)
+			.MinDesiredWidth(64.f)
+			.HAlign(HAlign_Center)
+			.Visibility(InArgs._ShowPlaybackRange ? EVisibility::Visible : EVisibility::Collapsed)
+			[
+				SNew(SBorder)
+				.Padding(0.f)
+				.BorderImage(nullptr)
+// 				.ForegroundColor(FLinearColor::Green)
+// 				[
+// 					//PlaybackRangeStart
+// 				]
+			]
+		]
+
+		+SHorizontalBox::Slot()
+		.VAlign(VAlign_Center)
+		.AutoWidth()
+		.Padding(2.f)
+		[
+			SNew(SBox)
+			.Visibility(InArgs._ShowViewRange ? EVisibility::Visible : EVisibility::Collapsed)
+			.MinDesiredWidth(64.f)
+// 			.HAlign(HAlign_Center)
+// 			[
+// 				//ViewRangeStart
+// 			]
+		]
+
+		+SHorizontalBox::Slot()
+			.FillWidth(1.0f)
+			.Padding(2.0f, 4.0f)
+			.VAlign(VAlign_Center)
+			[
+				InArgs._CenterContent.Widget
+			]
+		
+		+SHorizontalBox::Slot()
+		.VAlign(VAlign_Center)
+		.AutoWidth()
+		.Padding(2.f)
+		[
+			SNew(SBox)
+			.Visibility(InArgs._ShowViewRange ? EVisibility::Visible : EVisibility::Collapsed)
+			.MinDesiredWidth(64.f)
+// 			.HAlign(HAlign_Center)
+// 			[
+// 				//ViewRangeEnd
+// 			]
+		]
+
+		+SHorizontalBox::Slot()
+		.VAlign(VAlign_Center)
+		.AutoWidth()
+		.Padding(2.f)
+		[
+			SNew(SBox)
+			.MinDesiredWidth(64.f)
+			.HAlign(HAlign_Center)
+			.Visibility(InArgs._ShowPlaybackRange ? EVisibility::Visible : EVisibility::Collapsed)
+			[
+				SNew(SBorder)
+				.Padding(0.f)
+				.BorderImage(nullptr)
+// 				.ForegroundColor(FLinearColor::Red)
+// 				[
+// 					//PlaybackRangeEnd
+// 				]
+			]
+		]
+
+		+SHorizontalBox::Slot()
+		.VAlign(VAlign_Center)
+		.AutoWidth()
+		.Padding(2.f)
+		[
+			SNew(SBox)
+			.MinDesiredWidth(64.f)
+			.HAlign(HAlign_Center)
+			.Visibility(InArgs._ShowWorkingRange ? EVisibility::Visible : EVisibility::Collapsed)
+			[
+				WorkingRangeEnd
+			]
+		]
+	];
+}
+
+double SAbleTimeRange::WorkingStartTime() const
+{
+// 	FFrameRate Rate = TimeSliderController->GetTickResolution();
+// 	FFrameTime Time = TimeSliderController->GetClampRange().GetLowerBoundValue() * Rate;
+// 	return Time.GetFrame().Value;
+	return 0;
+}
+
+double SAbleTimeRange::WorkingEndTime() const
+{
+// 	FFrameRate Rate = TimeSliderController->GetTickResolution();
+// 	FFrameTime Time = TimeSliderController->GetClampRange().GetUpperBoundValue() * Rate;
+// 	return Time.GetFrame().Value;
+	return 100;
+}
+
+double SAbleTimeRange::ViewStartTime() const
+{
+	FFrameRate TickResolution = TimeSliderController->GetTickResolution();
+
+	// View range is in seconds so we convert it to tick resolution
+	FFrameTime Time = TimeSliderController->GetViewRange().GetLowerBoundValue() * TickResolution;
+	return Time.GetFrame().Value;
+}
+
+double SAbleTimeRange::ViewEndTime() const
+{
+	FFrameRate TickResolution = TimeSliderController->GetTickResolution();
+
+	// View range is in seconds so we convert it to tick resolution
+	FFrameTime Time = TimeSliderController->GetViewRange().GetUpperBoundValue() * TickResolution;
+	return Time.GetFrame().Value;
+}
+
+double SAbleTimeRange::GetSpinboxDelta() const
+{
+	return TimeSliderController->GetTickResolution().AsDecimal() * TimeSliderController->GetDisplayRate().AsInterval();
+}
+
+double SAbleTimeRange::PlayStartTime() const
+{
+	FFrameNumber LowerBound = UE::MovieScene::DiscreteInclusiveLower(TimeSliderController->GetPlayRange());
+	return LowerBound.Value; 
+}
+
+double SAbleTimeRange::PlayEndTime() const
+{
+	FFrameNumber UpperBound = UE::MovieScene::DiscreteExclusiveUpper(TimeSliderController->GetPlayRange());
+	return UpperBound.Value;
+}
+
+void SAbleTimeRange::OnWorkingStartTimeCommitted(double NewValue, ETextCommit::Type InTextCommit)
+{
+	OnWorkingStartTimeChanged(NewValue);
+}
+
+void SAbleTimeRange::OnWorkingEndTimeCommitted(double NewValue, ETextCommit::Type InTextCommit)
+{
+	OnWorkingEndTimeChanged(NewValue);
+}
+
+void SAbleTimeRange::OnViewStartTimeCommitted(double NewValue, ETextCommit::Type InTextCommit)
+{
+	OnViewStartTimeChanged(NewValue);
+}
+
+void SAbleTimeRange::OnViewEndTimeCommitted(double NewValue, ETextCommit::Type InTextCommit)
+{
+	OnViewEndTimeChanged(NewValue);
+}
+
+void SAbleTimeRange::OnPlayStartTimeCommitted(double NewValue, ETextCommit::Type InTextCommit)
+{
+	OnPlayStartTimeChanged(NewValue);
+}
+
+void SAbleTimeRange::OnPlayEndTimeCommitted(double NewValue, ETextCommit::Type InTextCommit)
+{
+	OnPlayEndTimeChanged(NewValue);
+}
+
+void SAbleTimeRange::OnWorkingStartTimeChanged(double NewValue)
+{
+	FFrameRate TickResolution = TimeSliderController->GetTickResolution();
+	double Time = TickResolution.AsSeconds(FFrameTime::FromDecimal(NewValue));
+
+	// Clamp range is in seconds
+	TimeSliderController->SetClampRange(Time, TimeSliderController->GetClampRange().GetUpperBoundValue());
+
+	if (Time > TimeSliderController->GetViewRange().GetLowerBoundValue())
+	{
+		OnViewStartTimeChanged(NewValue);
+	}
+}
+
+void SAbleTimeRange::OnWorkingEndTimeChanged(double NewValue)
+{
+	FFrameRate TickResolution = TimeSliderController->GetTickResolution();
+	double Time = TickResolution.AsSeconds(FFrameTime::FromDecimal(NewValue));
+
+	// Clamp range is in seconds
+	TimeSliderController->SetClampRange(TimeSliderController->GetClampRange().GetLowerBoundValue(), Time);
+
+	if (Time < TimeSliderController->GetViewRange().GetUpperBoundValue())
+	{
+		OnViewEndTimeChanged(NewValue);
+	}
+}
+
+void SAbleTimeRange::OnViewStartTimeChanged(double NewValue)
+{
+	FFrameRate TickResolution = TimeSliderController->GetTickResolution();
+	double Time = TickResolution.AsSeconds(FFrameTime::FromDecimal(NewValue));
+
+	double ViewStartTime = TimeSliderController->GetViewRange().GetLowerBoundValue();
+	double ViewEndTime = TimeSliderController->GetViewRange().GetUpperBoundValue();
+
+	double ClampStartTime = TimeSliderController.Get()->GetClampRange().GetLowerBoundValue();
+	double ClampEndTime = TimeSliderController.Get()->GetClampRange().GetUpperBoundValue();
+
+	if (Time >= ViewEndTime)
+	{
+		double ViewDuration = ViewEndTime - ViewStartTime;
+		ViewEndTime = Time + ViewDuration;
+
+		if (ViewEndTime > TimeSliderController.Get()->GetClampRange().GetUpperBoundValue())
+		{
+			TimeSliderController->SetClampRange(TimeSliderController->GetClampRange().GetLowerBoundValue(), ViewEndTime);
+		}
+	}
+
+
+	if (Time < TimeSliderController.Get()->GetClampRange().GetLowerBoundValue())
+	{
+		TimeSliderController->SetClampRange(Time, TimeSliderController->GetClampRange().GetUpperBoundValue());
+	}
+
+	TimeSliderController->SetViewRange(Time, ViewEndTime, EViewRangeInterpolation::Immediate);
+}
+
+
+void SAbleTimeRange::OnViewEndTimeChanged(double NewValue)
+{
+	FFrameRate TickResolution = TimeSliderController->GetTickResolution();
+	double Time = TickResolution.AsSeconds(FFrameTime::FromDecimal(NewValue));
+
+	double ViewStartTime = TimeSliderController->GetViewRange().GetLowerBoundValue();
+	double ViewEndTime = TimeSliderController->GetViewRange().GetUpperBoundValue();
+
+	if (Time <= ViewStartTime)
+	{
+		double ViewDuration = ViewEndTime - ViewStartTime;
+		ViewStartTime = Time - ViewDuration;
+
+		if (ViewStartTime < TimeSliderController.Get()->GetClampRange().GetLowerBoundValue())
+		{
+			TimeSliderController->SetClampRange(ViewStartTime, TimeSliderController->GetClampRange().GetUpperBoundValue());
+		}
+	}
+
+	if (Time > TimeSliderController->GetClampRange().GetUpperBoundValue())
+	{
+		TimeSliderController->SetClampRange(TimeSliderController->GetClampRange().GetLowerBoundValue(), Time);
+	}
+
+	TimeSliderController->SetViewRange(ViewStartTime, Time, EViewRangeInterpolation::Immediate);
+}
+
+void SAbleTimeRange::OnPlayStartTimeChanged(double NewValue)
+{
+	FFrameRate TickResolution = TimeSliderController->GetTickResolution();
+	FFrameTime Time = FFrameTime::FromDecimal(NewValue);
+	double     TimeInSeconds = TickResolution.AsSeconds(Time);
+
+	TRange<FFrameNumber> PlayRange = TimeSliderController->GetPlayRange();
+	FFrameNumber PlayDuration;
+	if (Time.FrameNumber >= UE::MovieScene::DiscreteExclusiveUpper(PlayRange))
+	{
+		PlayDuration = UE::MovieScene::DiscreteExclusiveUpper(PlayRange) - UE::MovieScene::DiscreteInclusiveLower(PlayRange);
+	}
+	else
+	{
+		PlayDuration = UE::MovieScene::DiscreteExclusiveUpper(PlayRange) - Time.FrameNumber;
+	}
+
+	TimeSliderController->SetPlayRange(Time.FrameNumber, PlayDuration.Value);
+
+	// Expand view ranges if outside of play range
+	if (TimeInSeconds < TimeSliderController.Get()->GetClampRange().GetLowerBoundValue())
+	{
+		OnViewStartTimeChanged(NewValue);
+	}
+
+	FFrameNumber PlayEnd = TimeSliderController->GetPlayRange().GetUpperBoundValue();
+	double PlayEndSeconds = PlayEnd / TickResolution;
+
+	if (PlayEndSeconds > TimeSliderController.Get()->GetClampRange().GetUpperBoundValue())
+	{
+		OnViewEndTimeChanged(TickResolution.AsFrameNumber(PlayEndSeconds).Value);
+	}
+}
+
+void SAbleTimeRange::OnPlayEndTimeChanged(double NewValue)
+{
+	FFrameRate TickResolution = TimeSliderController->GetTickResolution();
+	FFrameTime Time = FFrameTime::FromDecimal(NewValue);
+	double     TimeInSeconds = TickResolution.AsSeconds(Time);
+
+	TRange<FFrameNumber> PlayRange = TimeSliderController->GetPlayRange();
+	FFrameNumber PlayDuration;
+	FFrameNumber StartFrame = UE::MovieScene::DiscreteInclusiveLower(PlayRange);
+	if (Time.FrameNumber <= StartFrame)
+	{
+		PlayDuration = UE::MovieScene::DiscreteExclusiveUpper(PlayRange) - StartFrame;
+		StartFrame = Time.FrameNumber - PlayDuration;
+	}
+	else
+	{
+		PlayDuration = Time.FrameNumber - StartFrame;
+	}
+
+	TimeSliderController->SetPlayRange(StartFrame, PlayDuration.Value);
+
+	// Expand view ranges if outside of play range
+	if (TimeInSeconds > TimeSliderController->GetClampRange().GetUpperBoundValue())
+	{
+		OnViewEndTimeChanged(NewValue);
+	}
+
+	FFrameNumber PlayStart = TimeSliderController->GetPlayRange().GetLowerBoundValue();
+	double PlayStartSeconds = PlayStart / TickResolution;
+
+	if (PlayStartSeconds < TimeSliderController.Get()->GetClampRange().GetLowerBoundValue())
+	{
+		OnViewStartTimeChanged(TickResolution.AsFrameNumber(PlayStartSeconds).Value);
+	}
+}
+
+#undef LOCTEXT_NAMESPACE

+ 336 - 0
Ability/Plugins/EzAbility/Source/EzAbilityEditor/Private/SAbleTimeRangeSlider.cpp

@@ -0,0 +1,336 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "SAbleTimeRangeSlider.h"
+
+#include "AnimatedRange.h"
+#include "ITimeSlider.h"
+#include "Input/Events.h"
+#include "Layout/Geometry.h"
+#include "Math/Color.h"
+#include "Math/UnrealMathSSE.h"
+#include "Rendering/DrawElements.h"
+#include "Rendering/RenderingCommon.h"
+#include "Styling/AppStyle.h"
+#include "Styling/SlateColor.h"
+#include "Styling/WidgetStyle.h"
+#include "UObject/NameTypes.h"
+
+class FSlateRect;
+struct FSlateBrush;
+
+#define LOCTEXT_NAMESPACE "STimeRangeSlider"
+
+namespace TimeRangeSliderConstants
+{
+	const int32 HandleSize = 14;
+	const int32 MinimumScrubberWidth = HandleSize * 2;
+}
+
+void SAbleTimeRangeSlider::Construct( const FArguments& InArgs, TSharedRef<ITimeSliderController> InTimeSliderController)
+{
+	TimeSliderController = InTimeSliderController;
+	LastViewRange = TimeSliderController->GetViewRange();
+
+	ResetState();
+	ResetHoveredState();
+}
+
+double SAbleTimeRangeSlider::ComputeDragDelta(const FPointerEvent& MouseEvent, double GeometryWidth) const
+{
+	double StartTime = 0;
+	double EndTime = 0;
+
+	if (TimeSliderController.IsValid())
+	{
+		StartTime = TimeSliderController->GetClampRange().GetLowerBoundValue();
+		EndTime = TimeSliderController->GetClampRange().GetUpperBoundValue();
+	}
+	double DragDistance = (MouseEvent.GetScreenSpacePosition() - MouseDownPosition).X;
+
+	const double PixelToUnits = (EndTime - StartTime) / (GeometryWidth - TimeRangeSliderConstants::HandleSize*2);
+	return DragDistance * PixelToUnits;
+}
+
+void SAbleTimeRangeSlider::ComputeHandleOffsets(double& LeftHandleOffset, double& HandleOffset, double& RightHandleOffset, double GeometryWidth) const
+{
+	double StartTime = 0;
+	double InTime = 0;
+	double OutTime = 0;
+	double EndTime = 0;
+
+	if (TimeSliderController.IsValid())
+	{
+// 		StartTime = TimeSliderController->GetClampRange().GetLowerBoundValue();
+// 		InTime = TimeSliderController->GetViewRange().GetLowerBoundValue();
+// 		OutTime = TimeSliderController->GetViewRange().GetUpperBoundValue();
+// 		EndTime = TimeSliderController->GetClampRange().GetUpperBoundValue();
+		StartTime = 0;
+		InTime = 0;
+		OutTime = 0;
+		EndTime = 100;
+	}
+
+	const double UnitsToPixel = (GeometryWidth - TimeRangeSliderConstants::HandleSize*2) / (EndTime - StartTime);
+
+	LeftHandleOffset = (InTime - StartTime) * UnitsToPixel;
+	HandleOffset = LeftHandleOffset + TimeRangeSliderConstants::HandleSize;
+	RightHandleOffset = HandleOffset + (OutTime - InTime) * UnitsToPixel;
+	
+	double ScrubberWidth = RightHandleOffset-LeftHandleOffset-TimeRangeSliderConstants::HandleSize;
+	if (ScrubberWidth < (double)TimeRangeSliderConstants::MinimumScrubberWidth)
+	{
+		HandleOffset = HandleOffset - (TimeRangeSliderConstants::MinimumScrubberWidth - ScrubberWidth) / 2.0;
+		LeftHandleOffset = HandleOffset - TimeRangeSliderConstants::HandleSize;
+		RightHandleOffset = HandleOffset + TimeRangeSliderConstants::MinimumScrubberWidth;
+	}
+}
+
+
+FVector2D SAbleTimeRangeSlider::ComputeDesiredSize(float) const
+{
+	return FVector2D(4.0f * TimeRangeSliderConstants::HandleSize, TimeRangeSliderConstants::HandleSize);
+}
+
+
+int32 SAbleTimeRangeSlider::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const
+{
+	const int32 BackgroundLayer = LayerId+1;
+	const int32 SliderBoxLayer = BackgroundLayer+1;
+	const int32 HandleLayer = SliderBoxLayer+1;
+
+	static const FSlateBrush* RangeHandleLeft = FAppStyle::GetBrush( TEXT( "Sequencer.Timeline.RangeHandleLeft" ) ); 
+	static const FSlateBrush* RangeHandleRight = FAppStyle::GetBrush( TEXT( "Sequencer.Timeline.RangeHandleRight" ) ); 
+	static const FSlateBrush* RangeHandle = FAppStyle::GetBrush( TEXT( "Sequencer.Timeline.RangeHandle" ) ); 
+
+	double LeftHandleOffset = 0;
+	double HandleOffset = 0;
+	double RightHandleOffset = 0;
+	ComputeHandleOffsets(LeftHandleOffset, HandleOffset, RightHandleOffset, AllottedGeometry.GetLocalSize().X);
+
+	static const FName SelectionColorName("SelectionColor");
+	FLinearColor SelectionColor = FAppStyle::GetSlateColor(SelectionColorName).GetColor(FWidgetStyle());
+
+	// Draw the handle box
+	FSlateDrawElement::MakeBox( 
+		OutDrawElements,
+		LayerId, 
+		AllottedGeometry.ToPaintGeometry(FVector2f(RightHandleOffset-LeftHandleOffset-TimeRangeSliderConstants::HandleSize, TimeRangeSliderConstants::HandleSize), FSlateLayoutTransform(FVector2f(HandleOffset, 0.0f))),
+		RangeHandle,
+		ESlateDrawEffect::None,
+		(bHandleDragged || bHandleHovered) ? SelectionColor : FLinearColor::Gray);
+
+	// Draw the left handle box
+	FSlateDrawElement::MakeBox( 
+		OutDrawElements,
+		LayerId, 
+		AllottedGeometry.ToPaintGeometry(FVector2f(TimeRangeSliderConstants::HandleSize, TimeRangeSliderConstants::HandleSize), FSlateLayoutTransform(FVector2f(LeftHandleOffset, 0.0f))),
+		RangeHandleLeft,
+		ESlateDrawEffect::None,
+		(bLeftHandleDragged || bLeftHandleHovered) ? SelectionColor : FLinearColor::Gray);
+
+	// Draw the right handle box
+	FSlateDrawElement::MakeBox( 
+		OutDrawElements,
+		LayerId, 
+		AllottedGeometry.ToPaintGeometry(FVector2f(TimeRangeSliderConstants::HandleSize, TimeRangeSliderConstants::HandleSize), FSlateLayoutTransform(FVector2f(RightHandleOffset, 0.0f))),
+		RangeHandleRight,
+		ESlateDrawEffect::None,
+		(bRightHandleDragged || bRightHandleHovered) ? SelectionColor : FLinearColor::Gray);
+
+	SCompoundWidget::OnPaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, ShouldBeEnabled( bParentEnabled ));
+
+	return LayerId;
+}
+
+FReply SAbleTimeRangeSlider::OnMouseButtonDown( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
+{
+	MouseDownPosition = MouseEvent.GetScreenSpacePosition();
+	if (TimeSliderController.IsValid())
+	{		
+		MouseDownViewRange = TimeSliderController->GetViewRange();
+	}
+
+	if (bHandleHovered)
+	{
+		bHandleDragged = true;
+		return FReply::Handled().CaptureMouse(AsShared());
+	}
+	else if (bLeftHandleHovered)
+	{
+		bLeftHandleDragged = true;
+		return FReply::Handled().CaptureMouse(AsShared());
+	}
+	else if (bRightHandleHovered)
+	{
+		bRightHandleDragged = true;
+		return FReply::Handled().CaptureMouse(AsShared());
+	}
+
+	return FReply::Unhandled();
+}
+
+FReply SAbleTimeRangeSlider::OnMouseButtonUp( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
+{
+	ResetState();
+
+	return FReply::Handled().ReleaseMouseCapture();
+}
+
+FReply SAbleTimeRangeSlider::OnMouseMove( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
+{
+	if (HasMouseCapture())
+	{
+		double DragDelta = ComputeDragDelta(MouseEvent, MyGeometry.GetLocalSize().X);
+
+		ITimeSliderController* TimeSliderControllerPtr = TimeSliderController.Get();
+		if (!TimeSliderControllerPtr)
+		{
+			return FReply::Handled();
+		}
+
+		if (bHandleDragged)
+		{
+			double NewIn = MouseDownViewRange.GetLowerBoundValue() + DragDelta;
+			double NewOut = MouseDownViewRange.GetUpperBoundValue() + DragDelta;
+
+			TRange<double> ClampRange = TimeSliderControllerPtr->GetClampRange();
+			if (NewIn < ClampRange.GetLowerBoundValue())
+			{
+				NewIn = ClampRange.GetLowerBoundValue();
+				NewOut = NewIn + (MouseDownViewRange.GetUpperBoundValue() - MouseDownViewRange.GetLowerBoundValue());
+			}
+			else if (NewOut > ClampRange.GetUpperBoundValue())
+			{
+				NewOut = ClampRange.GetUpperBoundValue();
+				NewIn = NewOut - (MouseDownViewRange.GetUpperBoundValue() - MouseDownViewRange.GetLowerBoundValue());
+			}
+			
+			TimeSliderControllerPtr->SetViewRange(NewIn, NewOut, EViewRangeInterpolation::Immediate);
+		}
+		else if (bLeftHandleDragged || bRightHandleDragged)
+		{
+			double NewIn = 0;
+			double NewOut = 0;
+
+			if (bLeftHandleDragged)
+			{
+				NewIn = MouseDownViewRange.GetLowerBoundValue() + DragDelta;
+				
+				NewOut = MouseDownViewRange.GetUpperBoundValue();
+				if (MouseEvent.IsShiftDown()) 
+				{
+					NewOut -= DragDelta;
+				}
+			}
+			else
+			{
+				NewIn = MouseDownViewRange.GetLowerBoundValue();
+				if (MouseEvent.IsShiftDown())
+				{
+ 					NewIn -= DragDelta;
+				}
+
+				NewOut = MouseDownViewRange.GetUpperBoundValue() + DragDelta;
+			}
+
+			// In cases of extreme zoom the drag delta will be greater than the difference between In/Out.
+			// This causes zooming to then become pan at extreme levels which is undesirable.
+			if (NewIn >= NewOut)
+			{
+				return FReply::Handled();
+			}
+
+			TimeSliderControllerPtr->SetViewRange(NewIn, NewOut, EViewRangeInterpolation::Immediate);
+		}
+
+		return FReply::Handled();
+	}
+	else
+	{
+		ResetHoveredState();
+
+		double LeftHandleOffset = 0;
+		double HandleOffset = 0;
+		double RightHandleOffset = 0;
+		ComputeHandleOffsets(LeftHandleOffset, HandleOffset, RightHandleOffset, MyGeometry.GetLocalSize().X);
+		
+		FGeometry LeftHandleRect  = MyGeometry.MakeChild(FVector2D(TimeRangeSliderConstants::HandleSize, TimeRangeSliderConstants::HandleSize), FSlateLayoutTransform(FVector2D(LeftHandleOffset, 0)));
+		FGeometry RightHandleRect = MyGeometry.MakeChild(FVector2D(TimeRangeSliderConstants::HandleSize, TimeRangeSliderConstants::HandleSize), FSlateLayoutTransform(FVector2D(RightHandleOffset, 0)));
+		FGeometry HandleRect      = MyGeometry.MakeChild(FVector2D(RightHandleOffset-LeftHandleOffset-TimeRangeSliderConstants::HandleSize, TimeRangeSliderConstants::HandleSize), FSlateLayoutTransform(FVector2D(HandleOffset, 0)));
+
+		FVector2D LocalMousePosition = MouseEvent.GetScreenSpacePosition();
+
+		if (HandleRect.IsUnderLocation(LocalMousePosition))
+		{
+			bHandleHovered = true;
+		}
+		else if (LeftHandleRect.IsUnderLocation(LocalMousePosition))
+		{
+			bLeftHandleHovered = true;
+		}
+		else if (RightHandleRect.IsUnderLocation(LocalMousePosition))
+		{
+			bRightHandleHovered = true;
+		}
+	}
+	return FReply::Unhandled();
+}
+
+void SAbleTimeRangeSlider::OnMouseLeave( const FPointerEvent& MouseEvent )
+{
+	SCompoundWidget::OnMouseLeave(MouseEvent);
+
+	if (!HasMouseCapture())
+	{
+		ResetHoveredState();
+	}
+}
+
+FReply SAbleTimeRangeSlider::OnMouseButtonDoubleClick( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
+{
+	ResetState();
+
+	OnMouseMove(MyGeometry, MouseEvent);
+
+	if (bHandleHovered)
+	{
+		if (TimeSliderController.IsValid())
+		{
+			if (FMath::IsNearlyEqual(TimeSliderController->GetViewRange().GetLowerBoundValue(), TimeSliderController->GetClampRange().GetLowerBoundValue()) &&
+				FMath::IsNearlyEqual(TimeSliderController->GetViewRange().GetUpperBoundValue(), TimeSliderController->GetClampRange().GetUpperBoundValue()))
+			{
+				if (!LastViewRange.IsEmpty())
+				{
+					TimeSliderController->SetViewRange(LastViewRange.GetLowerBoundValue(), LastViewRange.GetUpperBoundValue(), EViewRangeInterpolation::Immediate);
+				}
+			}
+			else
+			{
+				LastViewRange = TimeSliderController->GetViewRange();
+				TimeSliderController->SetViewRange(TimeSliderController->GetClampRange().GetLowerBoundValue(), TimeSliderController->GetClampRange().GetUpperBoundValue(), EViewRangeInterpolation::Immediate);
+			}
+		}
+
+		ResetState();
+		return FReply::Handled();
+	}
+	ResetState();
+	return FReply::Unhandled();
+}
+
+void SAbleTimeRangeSlider::ResetState()
+{
+	bHandleDragged = false;
+	bLeftHandleDragged = false;
+	bRightHandleDragged = false;
+	ResetHoveredState();
+}
+
+void SAbleTimeRangeSlider::ResetHoveredState()
+{
+	bHandleHovered = false;
+	bLeftHandleHovered = false;
+	bRightHandleHovered = false;
+}
+
+#undef LOCTEXT_NAMESPACE

+ 228 - 0
Ability/Plugins/EzAbility/Source/EzAbilityEditor/Private/SEzAbilityTimeLine.cpp

@@ -0,0 +1,228 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+
+#include "SEzAbilityTimeLine.h"
+#include "ITimeSlider.h"
+#include "SlateOptMacros.h"
+#include "FrameNumberDisplayFormat.h"
+#include "Preferences/PersonaOptions.h"
+#include "FrameNumberNumericInterface.h"
+#include "Widgets/Input/SNumericEntryBox.h"
+#include "ISequencerWidgetsModule.h"
+
+
+#define LOCTEXT_NAMESPACE "AblAbilityTimeLine"
+
+void SEzAbilityTimeLine::Construct(const FArguments& InArgs)
+{
+	const TAttribute<EFrameNumberDisplayFormats> DisplayFormat = MakeAttributeLambda([]()
+		{
+			return GetDefault<UPersonaOptions>()->TimelineDisplayFormat;
+		});
+	const TAttribute<FFrameRate> TickResolution = MakeAttributeLambda([this]()
+		{
+			return FFrameRate();
+		});
+
+	const TAttribute<FFrameRate> DisplayRate = MakeAttributeLambda([this]()
+		{
+			return FFrameRate();
+		});
+
+	const TAttribute<EFrameNumberDisplayFormats> DisplayFormatSecondary = MakeAttributeLambda([]()
+		{
+			return GetDefault<UPersonaOptions>()->TimelineDisplayFormat == EFrameNumberDisplayFormats::Frames ? EFrameNumberDisplayFormats::Seconds : EFrameNumberDisplayFormats::Frames;
+		});
+
+
+	NumericTypeInterface = MakeShareable(new FFrameNumberInterface(DisplayFormat, 0, TickResolution, DisplayRate));
+	SecondaryNumericTypeInterface = MakeShareable(new FFrameNumberInterface(DisplayFormatSecondary, 0, TickResolution, DisplayRate));
+
+	FTimeSliderArgs TimeSliderArgs;
+	{
+		TimeSliderArgs.ScrubPosition = FFrameTime(0);
+		TimeSliderArgs.ViewRange = FAnimatedRange(0.0, 100.0);
+		TimeSliderArgs.PlaybackRange = TRange<FFrameNumber>(0, 100);
+		TimeSliderArgs.ClampRange = FAnimatedRange(0.0, 100.0);
+		TimeSliderArgs.DisplayRate = DisplayRate;
+		TimeSliderArgs.TickResolution = TickResolution;
+		TimeSliderArgs.IsPlaybackRangeLocked = true;
+		TimeSliderArgs.PlaybackStatus = EMovieScenePlayerStatus::Stopped;
+		TimeSliderArgs.NumericTypeInterface = NumericTypeInterface;
+	}
+
+	TimeSliderController = MakeShareable(new FEZAbleSliderController(TimeSliderArgs, SharedThis(this), SecondaryNumericTypeInterface));
+
+	TSharedRef<FEZAbleSliderController> TimeSliderControllerRef = TimeSliderController.ToSharedRef();
+
+	TopTimeSlider = CreateTimeSlider(TimeSliderControllerRef, false);
+
+
+	TSharedRef<ITimeSlider> BottomTimeRange = CreateTimeRange(
+		FTimeRangeArgs(
+			EShowRange::ViewRange | EShowRange::WorkingRange | EShowRange::PlaybackRange,
+			EShowRange::ViewRange | EShowRange::WorkingRange,
+			TimeSliderControllerRef,
+			EVisibility::Visible,
+			NumericTypeInterface.ToSharedRef()
+		),
+		CreateTimeRangeSlider(TimeSliderControllerRef)
+	);
+
+	ChildSlot
+		[
+			SNew(SBorder)
+				.BorderImage(FCoreStyle::Get().GetBrush("ToolPanel.GroupBorder"))
+				[
+					SNew(SVerticalBox)
+						+ SVerticalBox::Slot()
+						.AutoHeight()
+						.VAlign(VAlign_Top)
+						[
+							SNew(SHorizontalBox)
+								+ SHorizontalBox::Slot()
+								.FillWidth(0.26f)
+								.HAlign(HAlign_Fill)
+								[
+									BuildTimelineControls()
+								]
+								+ SHorizontalBox::Slot()
+								.FillWidth(0.74f)
+								.HAlign(EHorizontalAlignment::HAlign_Fill)
+								[
+									//SNew(SSequencerTimeSlider, TimeSliderController)
+									//SNew(SEzAbilityTimelineScrubPanel)
+									//BottomTimeRange
+									TopTimeSlider.ToSharedRef()
+								]//debug code.
+						]
+
+				]
+		];
+}
+
+TSharedRef<SWidget> SEzAbilityTimeLine::BuildTimelineControls()
+{
+	return
+		SNew(SBorder)
+		.BorderImage(FCoreStyle::Get().GetBrush("ToolPanel.GroupBorder"))
+		[
+			SNew(SHorizontalBox)
+				+ SHorizontalBox::Slot()
+				.Padding(1.0f, 2.0f)
+				.HAlign(HAlign_Left)
+				.AutoWidth()
+				[
+					SNew(SButton)
+						.ButtonStyle(FAppStyle::Get(), "NoBorder")
+						//.ToolTipText(LOCTEXT("StepBack", "Step Back"))
+						.ContentPadding(2.0f)
+						[
+							SNew(SImage)
+								.Image(FAppStyle::GetBrush("WhiteBrush"))
+						]
+				]
+				+ SHorizontalBox::Slot()
+				.Padding(1.0f, 2.0f)
+				.HAlign(HAlign_Left)
+				.AutoWidth()
+				[
+					SNew(SButton)
+						.ButtonStyle(FAppStyle::Get(), "NoBorder")
+						//.ToolTipText(LOCTEXT("Stop", "Stop"))
+						.ContentPadding(2.0f)
+						[
+							SNew(SImage)
+								.Image(FAppStyle::GetBrush("WhiteBrush"))
+						]
+				]
+				+ SHorizontalBox::Slot()
+				.Padding(1.0f, 2.0f)
+				.HAlign(HAlign_Left)
+				.AutoWidth()
+				[
+					SNew(SButton)
+						.ButtonStyle(FAppStyle::Get(), "NoBorder")
+						//.OnClicked(this, &SAblAbilityTimelinePanel::OnPlayOrPause)
+						//.ToolTipText(this, &SAblAbilityTimelinePanel::GetPlayPauseToolTip)
+						.ContentPadding(2.0f)
+						[
+							SNew(SImage)
+								.Image(FAppStyle::GetBrush("WhiteBrush"))
+						]
+				]
+				+ SHorizontalBox::Slot()
+				.Padding(1.0f, 2.0f)
+				.HAlign(HAlign_Left)
+				.AutoWidth()
+				[
+					SNew(SButton)
+						.ButtonStyle(FAppStyle::Get(), "NoBorder")
+						//.OnClicked(this, &SAblAbilityTimelinePanel::OnStepForwards)
+						//.ToolTipText(LOCTEXT("StepForward", "Step Forward"))
+						.ContentPadding(2.0f)
+						[
+							SNew(SImage)
+								.Image(FAppStyle::GetBrush("WhiteBrush"))
+						]
+				]
+
+				+ SHorizontalBox::Slot()
+				.HAlign(HAlign_Left)
+				.AutoWidth()
+				[
+					SNew(SSpacer)
+						.Size(FVector2D(16.0f, 1.0f))
+				]
+
+				+ SHorizontalBox::Slot()
+				.HAlign(HAlign_Left)
+				.VAlign(VAlign_Center)
+				.AutoWidth()
+				[
+					SNew(SNumericEntryBox<float>)
+						.Font(FAppStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont")))
+						.AllowSpin(false)
+						//.MinValue(this, &SAblAbilityTimelinePanel::GetTimeMin)
+						//.MaxValue(this, &SAblAbilityTimelinePanel::GetTimeMax)
+						//.Value(this, &SAblAbilityTimelinePanel::GetCurrentTime)
+						//.OnValueCommitted(this, &SAblAbilityTimelinePanel::OnTimeValueCommitted)
+						//.ToolTipText(this, &SAblAbilityTimelinePanel::GetTimeTooltipText)
+						.Label()
+						[
+							SNumericEntryBox<float>::BuildLabel(LOCTEXT("AblTimelineTimeLabel", "Time"), FLinearColor::White, FLinearColor(0.2f, 0.2f, 0.2f))
+						]
+				]
+
+				+ SHorizontalBox::Slot()
+				.HAlign(HAlign_Left)
+				.AutoWidth()
+				[
+					SNew(SSpacer)
+						.Size(FVector2D(16.0f, 1.0f))
+				]
+
+				+ SHorizontalBox::Slot()
+				.HAlign(HAlign_Left)
+				.VAlign(VAlign_Center)
+				.AutoWidth()
+				[
+					SNew(SNumericEntryBox<int>)
+						.Font(FAppStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont")))
+						.AllowSpin(true)
+						//.MinValue(this, &SAblAbilityTimelinePanel::GetFrameMin)
+						//.MaxValue(this, &SAblAbilityTimelinePanel::GetFrameMax)
+						//.Value(this, &SAblAbilityTimelinePanel::GetCurrentFrame)
+						//.OnValueCommitted(this, &SAblAbilityTimelinePanel::OnFrameValueCommitted)
+						//.ToolTipText(this, &SAblAbilityTimelinePanel::GetFramesTooltipText)
+						.Label()
+						[
+							SNumericEntryBox<float>::BuildLabel(LOCTEXT("AblTimelineFrameLabel", "Frame"), FLinearColor::White, FLinearColor(0.2f, 0.2f, 0.2f))
+						]
+				]
+		];
+}
+
+#undef LOCTEXT_NAMESPACE
+
+

+ 65 - 0
Ability/Plugins/EzAbility/Source/EzAbilityEditor/Public/EZAbleTimeSliderController.h

@@ -0,0 +1,65 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "UObject/NoExportTypes.h"
+#include "ITimeSlider.h"
+#include "TimeSliderArgs.h"
+#include "EzAbilityTimelinePanel.h"
+#include "Widgets/Input/NumericTypeInterface.h"
+#include "Styling/SlateBrush.h"
+#include "SEzAbilityTimeLine.h"
+
+class SEzAbilityTimeLine;
+
+class FEZAbleSliderController : public ITimeSliderController
+{
+
+private:
+
+	struct FDrawAbleTickArgs;
+	struct FAbleScrubRangeToScreen;
+
+public:
+
+	FEZAbleSliderController(const FTimeSliderArgs& InArgs,  TWeakPtr<SEzAbilityTimeLine> InWeakTimeline, TSharedPtr<INumericTypeInterface<double>> InSecondaryNumericTypeInterface );
+
+	void DrawTicks( FSlateWindowElementList& OutDrawElements, const TRange<double>& ViewRange, const FAbleScrubRangeToScreen& RangeToScreen, FDrawAbleTickArgs& InArgs ) const;
+	int32 DrawSelectionRange(const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FAbleScrubRangeToScreen& RangeToScreen, const FPaintPlaybackRangeArgs& Args) const;
+	int32 DrawPlaybackRange(const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FAbleScrubRangeToScreen& RangeToScreen, const FPaintPlaybackRangeArgs& Args) const;
+
+
+
+	virtual int32 OnPaintTimeSlider(bool bMirrorLabels, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override;
+	virtual int32 OnPaintViewArea(const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, bool bEnabled, const FPaintViewAreaArgs& Args) const override;
+	virtual FReply OnMouseButtonDown(SWidget& WidgetOwner, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
+	virtual FReply OnMouseButtonUp(SWidget& WidgetOwner, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
+	virtual FReply OnMouseMove(SWidget& WidgetOwner, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
+	virtual FReply OnMouseWheel(SWidget& WidgetOwner, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
+	virtual FFrameRate GetDisplayRate() const override { return TimeSliderArgs.DisplayRate.Get(); }
+	virtual FFrameRate GetTickResolution() const override { return TimeSliderArgs.TickResolution.Get(); }
+
+	int32 DrawEditableTimes(const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FAbleScrubRangeToScreen& RangeToScreen) const;
+
+
+private:
+	FTimeSliderArgs TimeSliderArgs;
+
+		/** Pointer back to the timeline */
+	TWeakPtr<SEzAbilityTimeLine> WeakTimeline;
+
+	/** Brush for drawing the fill area on the scrubber */
+	const FSlateBrush* ScrubFillBrush;
+
+	/** Brush for drawing an upwards facing scrub handles */
+	const FSlateBrush* ScrubHandleUpBrush;
+
+	/** Brush for drawing a downwards facing scrub handle */
+	const FSlateBrush* ScrubHandleDownBrush;
+
+	/** Brush for drawing an editable time */
+	const FSlateBrush* EditableTimeBrush;
+
+	TSharedPtr<INumericTypeInterface<double>> SecondaryNumericTypeInterface;
+};

+ 53 - 0
Ability/Plugins/EzAbility/Source/EzAbilityEditor/Public/SAbleSequencerTimeSlider.h

@@ -0,0 +1,53 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "HAL/Platform.h"
+#include "ITimeSlider.h"
+#include "Input/CursorReply.h"
+#include "Input/Reply.h"
+#include "Math/Vector2D.h"
+#include "Templates/SharedPointer.h"
+#include "Widgets/DeclarativeSyntaxSupport.h"
+
+class FPaintArgs;
+class FSlateRect;
+class FSlateWindowElementList;
+class FWidgetStyle;
+struct FGeometry;
+struct FPointerEvent;
+
+class SAbleSequencerTimeSlider : public ITimeSlider
+{
+public:
+
+	SLATE_BEGIN_ARGS(SAbleSequencerTimeSlider)
+		: _MirrorLabels( false )
+	{}
+		/* If we should mirror the labels on the timeline */
+		SLATE_ARGUMENT( bool, MirrorLabels )
+	SLATE_END_ARGS()
+
+
+	/**
+	 * Construct the widget
+	 * 
+	 * @param InArgs   A declaration from which to construct the widget
+	 */
+	void Construct( const FArguments& InArgs, TSharedRef<ITimeSliderController> InTimeSliderController );
+
+protected:
+	// SWidget interface
+	virtual FVector2D ComputeDesiredSize(float) const override;
+	virtual int32 OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const override;
+	virtual FReply OnMouseButtonDown( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) override;
+	virtual FReply OnMouseButtonUp( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) override;
+	virtual FReply OnMouseButtonDoubleClick(const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent) override;
+	virtual FReply OnMouseMove( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) override;
+	virtual FReply OnMouseWheel( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) override;
+	virtual FCursorReply OnCursorQuery(const FGeometry& MyGeometry, const FPointerEvent& CursorEvent) const override;
+private:
+	TSharedPtr<ITimeSliderController> TimeSliderController;
+	bool bMirrorLabels;
+};
+

+ 80 - 0
Ability/Plugins/EzAbility/Source/EzAbilityEditor/Public/SAbleTimeRange.h

@@ -0,0 +1,80 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "ITimeSlider.h"
+#include "Templates/SharedPointer.h"
+#include "Types/SlateEnums.h"
+#include "Widgets/DeclarativeSyntaxSupport.h"
+
+class SWidget;
+template <typename NumericType> struct INumericTypeInterface;
+
+class SAbleTimeRange : public ITimeSlider
+{
+public:
+	SLATE_BEGIN_ARGS(SAbleTimeRange)
+		: _ShowWorkingRange(true), _ShowViewRange(false), _ShowPlaybackRange(true)
+	{}
+		/** Whether to show the working range */
+		SLATE_ARGUMENT( bool, ShowWorkingRange )
+		/** Whether to show the view range */
+		SLATE_ARGUMENT( bool, ShowViewRange )
+		/** Whether to show the playback range */
+		SLATE_ARGUMENT( bool, ShowPlaybackRange )
+		/** Whether to enable the working range */
+		SLATE_ARGUMENT( bool, EnableWorkingRange )
+		/** Whether to enable the view range */
+		SLATE_ARGUMENT( bool, EnableViewRange )
+		/** Whether to enable the playback range */
+		SLATE_ARGUMENT( bool, EnablePlaybackRange )
+		/* Content to display inside the time range */
+		SLATE_DEFAULT_SLOT( FArguments, CenterContent )
+	SLATE_END_ARGS()
+
+	/**
+	 * Construct the widget
+	 * 
+	 * @param InArgs   A declaration from which to construct the widget
+	 */
+	void Construct( const FArguments& InArgs, TSharedRef<ITimeSliderController> InTimeSliderController, TSharedRef<INumericTypeInterface<double>> NumericTypeInterface );
+
+protected:
+	double GetSpinboxDelta() const;
+	
+protected:
+
+	double PlayStartTime() const;
+	double PlayEndTime() const;
+
+	void OnPlayStartTimeCommitted(double NewValue, ETextCommit::Type InTextCommit);
+	void OnPlayEndTimeCommitted(double NewValue, ETextCommit::Type InTextCommit);
+
+	void OnPlayStartTimeChanged(double NewValue);
+	void OnPlayEndTimeChanged(double NewValue);
+
+protected:
+
+	double ViewStartTime() const;
+	double ViewEndTime() const;
+	
+	void OnViewStartTimeCommitted(double NewValue, ETextCommit::Type InTextCommit);
+	void OnViewEndTimeCommitted(double NewValue, ETextCommit::Type InTextCommit);
+
+	void OnViewStartTimeChanged(double NewValue);
+	void OnViewEndTimeChanged(double NewValue);
+
+protected:
+
+	double WorkingStartTime() const;
+	double WorkingEndTime() const;
+
+	void OnWorkingStartTimeCommitted(double NewValue, ETextCommit::Type InTextCommit);
+	void OnWorkingEndTimeCommitted(double NewValue, ETextCommit::Type InTextCommit);
+
+	void OnWorkingStartTimeChanged(double NewValue);
+	void OnWorkingEndTimeChanged(double NewValue);
+
+private:
+	TSharedPtr<ITimeSliderController> TimeSliderController;
+};

+ 71 - 0
Ability/Plugins/EzAbility/Source/EzAbilityEditor/Public/SAbleTimeRangeSlider.h

@@ -0,0 +1,71 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "HAL/Platform.h"
+#include "Input/Reply.h"
+#include "Math/Range.h"
+#include "Math/Vector2D.h"
+#include "Templates/SharedPointer.h"
+#include "Widgets/DeclarativeSyntaxSupport.h"
+#include "Widgets/SCompoundWidget.h"
+
+class FPaintArgs;
+class FSlateRect;
+class FSlateWindowElementList;
+class FWidgetStyle;
+class ITimeSliderController;
+class SWidget;
+struct FGeometry;
+struct FPointerEvent;
+
+class SAbleTimeRangeSlider : public SCompoundWidget
+{
+public:
+	SLATE_BEGIN_ARGS(SAbleTimeRangeSlider){}
+		SLATE_DEFAULT_SLOT(FArguments, Content)
+	SLATE_END_ARGS()
+
+	void Construct( const FArguments& InArgs, TSharedRef<ITimeSliderController> InTimeSliderController );
+
+	// SWidget interface
+	virtual FVector2D ComputeDesiredSize(float) const override;
+	virtual int32 OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const override;
+	virtual FReply OnMouseButtonDown( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) override;
+	virtual FReply OnMouseButtonUp( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) override;
+	virtual FReply OnMouseMove( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) override;
+	virtual void OnMouseLeave( const FPointerEvent& MouseEvent ) override;
+	virtual FReply OnMouseButtonDoubleClick( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) override;
+
+protected:
+	void ResetState();
+	void ResetHoveredState();
+	double ComputeDragDelta(const FPointerEvent& MouseEvent, double GeometryWidth) const;
+	void ComputeHandleOffsets(double& LeftHandleOffset, double& RightHandleOffset, double&HandleOffset, double GeometryWidth) const;
+
+private:
+	/* The left handle is being dragged */
+	bool bLeftHandleDragged;
+	/* The right handle is being dragged */
+	bool bRightHandleDragged;
+	/* The handle is being dragged */
+	bool bHandleDragged;
+
+	/* The left handle is hovered */
+	bool bLeftHandleHovered;
+	/* The right handle is hovered */
+	bool bRightHandleHovered;
+	/* The handle is hovered */
+	bool bHandleHovered;
+
+	/* The position of the mouse on mouse down */
+	FVector2D MouseDownPosition;
+
+	/* The in/out view range on mouse down */
+	TRange<double> MouseDownViewRange;
+
+	/* The in/out view range viewed before expansion */
+	TRange<double> LastViewRange;
+
+	TSharedPtr<ITimeSliderController> TimeSliderController;
+};

+ 80 - 0
Ability/Plugins/EzAbility/Source/EzAbilityEditor/Public/SEzAbilityTimeLine.h

@@ -0,0 +1,80 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Widgets/SCompoundWidget.h"
+#include "EZAbleTimeSliderController.h"
+#include "SAbleSequencerTimeSlider.h"
+#include "SAbleTimeRangeSlider.h"
+#include "ITimeSlider.h"
+#include "SAbleTimeRange.h"
+#include "TimeSliderArgs.h"
+#include "ISequencerWidgetsModule.h"
+
+/**
+ * 
+ */
+ //EZABILITYEDITOR_API
+class  SEzAbilityTimeLine : public SCompoundWidget
+{
+public:
+	SLATE_BEGIN_ARGS(SEzAbilityTimeLine)
+	{}
+	/** The current total range of frame indices */
+	SLATE_ATTRIBUTE(FInt32Interval, ViewIndices)
+
+	/** Called when any widget contained within the anim timeline has received focus */
+	//SLATE_EVENT(FSimpleDelegate, OnReceivedFocus)
+	
+	SLATE_END_ARGS()
+
+
+public:
+
+	/** Constructs this widget with InArgs */
+	void Construct(const FArguments& InArgs);
+
+	TSharedRef<ITimeSlider> CreateTimeSlider(const TSharedRef<ITimeSliderController>& InController, bool bMirrorLabels)
+	{
+		return SNew(SAbleSequencerTimeSlider, InController)
+			.MirrorLabels(bMirrorLabels);
+	}
+
+	TSharedRef<SWidget> CreateTimeRangeSlider(const TSharedRef<class ITimeSliderController>& InController)
+	{
+		return SNew(SAbleTimeRangeSlider, InController);
+	}
+
+	TSharedRef<ITimeSlider> CreateTimeRange(const FTimeRangeArgs& InArgs, const TSharedRef<SWidget>& Content)
+	{
+		return SNew(SAbleTimeRange, InArgs.Controller, InArgs.NumericTypeInterface)
+			.Visibility(InArgs.VisibilityDelegate)
+			.ShowWorkingRange(!!(InArgs.ShowRanges & EShowRange::WorkingRange))
+			.ShowViewRange(!!(InArgs.ShowRanges & EShowRange::ViewRange))
+			.ShowPlaybackRange(!!(InArgs.ShowRanges & EShowRange::PlaybackRange))
+			.EnableWorkingRange(!!(InArgs.EnableRanges & EShowRange::WorkingRange))
+			.EnableViewRange(!!(InArgs.EnableRanges & EShowRange::ViewRange))
+			.EnablePlaybackRange(!!(InArgs.EnableRanges & EShowRange::PlaybackRange))
+			[
+				Content
+			];
+	}
+
+	TSharedRef<SWidget> BuildTimelineControls();
+
+private:
+	/** The top time slider widget */
+	TSharedPtr<ITimeSlider> TopTimeSlider;
+
+	TSharedPtr<FEZAbleSliderController> TimeSliderController;
+
+	/** The range of indices our track should display */
+	TAttribute<FInt32Interval> ViewIndices;
+
+	/** Numeric Type interface for converting between frame numbers and display formats. */
+	TSharedPtr<INumericTypeInterface<double>> NumericTypeInterface;
+
+	/** Secondary numeric Type interface for converting between frame numbers and display formats. */
+	TSharedPtr<INumericTypeInterface<double>> SecondaryNumericTypeInterface;
+};