2014-12-07 19:09:38 -05:00
|
|
|
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
|
2014-03-14 14:13:41 -04:00
|
|
|
|
|
|
|
|
#include "SequencerPrivatePCH.h"
|
|
|
|
|
#include "TimeSliderController.h"
|
|
|
|
|
|
|
|
|
|
#define LOCTEXT_NAMESPACE "TimeSlider"
|
|
|
|
|
|
|
|
|
|
namespace ScrubConstants
|
|
|
|
|
{
|
|
|
|
|
/** The minimum amount of pixels between each major ticks on the widget */
|
|
|
|
|
const int32 MinPixelsPerDisplayTick = 5;
|
|
|
|
|
|
|
|
|
|
/**The smallest number of units between between major tick marks */
|
|
|
|
|
const float MinDisplayTickSpacing = 0.001f;
|
2015-06-12 08:55:27 -04:00
|
|
|
|
|
|
|
|
/**The fraction of the current view range to scroll per unit delta */
|
|
|
|
|
const float ScrollPanFraction = 0.1f;
|
2014-03-14 14:13:41 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Utility struct for converting between scrub range space and local/absolute screen space */
|
|
|
|
|
struct FScrubRangeToScreen
|
|
|
|
|
{
|
|
|
|
|
FVector2D WidgetSize;
|
|
|
|
|
|
|
|
|
|
TRange<float> ViewInput;
|
|
|
|
|
float ViewInputRange;
|
|
|
|
|
float PixelsPerInput;
|
|
|
|
|
|
|
|
|
|
FScrubRangeToScreen(TRange<float> InViewInput, const FVector2D& InWidgetSize )
|
|
|
|
|
{
|
|
|
|
|
WidgetSize = InWidgetSize;
|
|
|
|
|
|
|
|
|
|
ViewInput = InViewInput;
|
|
|
|
|
ViewInputRange = ViewInput.Size<float>();
|
|
|
|
|
PixelsPerInput = ViewInputRange > 0 ? ( WidgetSize.X / ViewInputRange ) : 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Local Widget Space -> Curve Input domain. */
|
|
|
|
|
float LocalXToInput(float ScreenX) const
|
|
|
|
|
{
|
|
|
|
|
float LocalX = ScreenX;
|
|
|
|
|
return (LocalX/PixelsPerInput) + ViewInput.GetLowerBoundValue();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Curve Input domain -> local Widget Space */
|
|
|
|
|
float InputToLocalX(float Input) const
|
|
|
|
|
{
|
|
|
|
|
return (Input - ViewInput.GetLowerBoundValue()) * PixelsPerInput;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Gets the the next spacing value in the series
|
|
|
|
|
* to determine a good spacing value
|
|
|
|
|
* E.g, .001,.005,.010,.050,.100,.500,1.000,etc
|
|
|
|
|
*/
|
|
|
|
|
static float GetNextSpacing( uint32 CurrentStep )
|
|
|
|
|
{
|
|
|
|
|
if(CurrentStep & 0x01)
|
|
|
|
|
{
|
|
|
|
|
// Odd numbers
|
|
|
|
|
return FMath::Pow( 10.f, 0.5f*((float)(CurrentStep-1)) + 1.f );
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Even numbers
|
|
|
|
|
return 0.5f * FMath::Pow( 10.f, 0.5f*((float)(CurrentStep)) + 1.f );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Determines the optimal spacing between tick marks in the slider for a given pixel density
|
|
|
|
|
* Increments until a minimum amount of slate units specified by MinTick is reached
|
|
|
|
|
*
|
|
|
|
|
* @param InPixelsPerInput The density of pixels between each input
|
|
|
|
|
* @param MinTick The minimum slate units per tick allowed
|
|
|
|
|
* @param MinTickSpacing The minimum tick spacing in time units allowed
|
|
|
|
|
* @return the optimal spacing in time units
|
|
|
|
|
*/
|
|
|
|
|
float DetermineOptimalSpacing( float InPixelsPerInput, uint32 MinTick, float MinTickSpacing )
|
|
|
|
|
{
|
|
|
|
|
uint32 CurStep = 0;
|
|
|
|
|
|
|
|
|
|
// Start with the smallest spacing
|
|
|
|
|
float Spacing = MinTickSpacing;
|
|
|
|
|
|
2015-03-24 18:45:10 -04:00
|
|
|
if (InPixelsPerInput > 0)
|
2014-03-14 14:13:41 -04:00
|
|
|
{
|
2015-03-24 18:45:10 -04:00
|
|
|
while( Spacing * InPixelsPerInput < MinTick )
|
|
|
|
|
{
|
|
|
|
|
Spacing = MinTickSpacing * GetNextSpacing( CurStep );
|
|
|
|
|
CurStep++;
|
|
|
|
|
}
|
2014-03-14 14:13:41 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Spacing;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
FSequencerTimeSliderController::FSequencerTimeSliderController( const FTimeSliderArgs& InArgs )
|
|
|
|
|
: TimeSliderArgs( InArgs )
|
|
|
|
|
, DistanceDragged( 0.0f )
|
|
|
|
|
, bDraggingScrubber( false )
|
|
|
|
|
, bPanning( false )
|
|
|
|
|
{
|
|
|
|
|
ScrubHandleUp = FEditorStyle::GetBrush( TEXT( "Sequencer.Timeline.ScrubHandleUp" ) );
|
|
|
|
|
ScrubHandleDown = FEditorStyle::GetBrush( TEXT( "Sequencer.Timeline.ScrubHandleDown" ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct FDrawTickArgs
|
|
|
|
|
{
|
|
|
|
|
/** Geometry of the area */
|
|
|
|
|
FGeometry AllottedGeometry;
|
|
|
|
|
/** Clipping rect of the area */
|
|
|
|
|
FSlateRect ClippingRect;
|
|
|
|
|
/** 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::Type DrawEffects;
|
|
|
|
|
/** Whether or not to only draw major ticks */
|
|
|
|
|
bool bOnlyDrawMajorTicks;
|
|
|
|
|
/** Whether or not to mirror labels */
|
|
|
|
|
bool bMirrorLabels;
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
2014-11-22 13:47:05 -05:00
|
|
|
void FSequencerTimeSliderController::DrawTicks( FSlateWindowElementList& OutDrawElements, const struct FScrubRangeToScreen& RangeToScreen, FDrawTickArgs& InArgs ) const
|
2014-03-14 14:13:41 -04:00
|
|
|
{
|
|
|
|
|
const float Spacing = DetermineOptimalSpacing( RangeToScreen.PixelsPerInput, ScrubConstants::MinPixelsPerDisplayTick, ScrubConstants::MinDisplayTickSpacing );
|
|
|
|
|
|
|
|
|
|
// Sub divisions
|
|
|
|
|
// @todo Sequencer may need more robust calculation
|
|
|
|
|
const int32 Divider = 10;
|
|
|
|
|
// For slightly larger halfway tick mark
|
|
|
|
|
const int32 HalfDivider = Divider / 2;
|
|
|
|
|
// Find out where to start from
|
2014-05-06 06:26:25 -04:00
|
|
|
int32 OffsetNum = FMath::FloorToInt(RangeToScreen.ViewInput.GetLowerBoundValue() / Spacing);
|
2014-03-14 14:13:41 -04:00
|
|
|
|
|
|
|
|
FSlateFontInfo SmallLayoutFont( FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Regular.ttf"), 8 );
|
|
|
|
|
|
|
|
|
|
TArray<FVector2D> LinePoints;
|
|
|
|
|
LinePoints.AddUninitialized(2);
|
|
|
|
|
|
|
|
|
|
float Seconds = 0;
|
|
|
|
|
while( (Seconds = OffsetNum*Spacing) < RangeToScreen.ViewInput.GetUpperBoundValue() )
|
|
|
|
|
{
|
|
|
|
|
// X position local to start of the widget area
|
|
|
|
|
float XPos = RangeToScreen.InputToLocalX( Seconds );
|
|
|
|
|
uint32 AbsOffsetNum = FMath::Abs(OffsetNum);
|
|
|
|
|
|
|
|
|
|
if ( AbsOffsetNum % Divider == 0 )
|
|
|
|
|
{
|
|
|
|
|
FVector2D Offset( XPos, InArgs.TickOffset );
|
2015-02-16 11:59:08 -05:00
|
|
|
FVector2D TickSize( 0.0f, InArgs.MajorTickHeight );
|
2014-03-14 14:13:41 -04:00
|
|
|
|
2015-02-16 11:59:08 -05:00
|
|
|
LinePoints[0] = FVector2D( 0.0f,1.0f);
|
2014-03-14 14:13:41 -04:00
|
|
|
LinePoints[1] = TickSize;
|
|
|
|
|
|
|
|
|
|
// lines should not need anti-aliasing
|
|
|
|
|
const bool bAntiAliasLines = false;
|
|
|
|
|
|
|
|
|
|
// Draw each tick mark
|
|
|
|
|
FSlateDrawElement::MakeLines(
|
|
|
|
|
OutDrawElements,
|
|
|
|
|
InArgs.StartLayer,
|
|
|
|
|
InArgs.AllottedGeometry.ToPaintGeometry( Offset, TickSize ),
|
|
|
|
|
LinePoints,
|
|
|
|
|
InArgs.ClippingRect,
|
|
|
|
|
InArgs.DrawEffects,
|
|
|
|
|
InArgs.TickColor,
|
2015-02-16 11:59:08 -05:00
|
|
|
bAntiAliasLines
|
2014-03-14 14:13:41 -04:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if( !InArgs.bOnlyDrawMajorTicks )
|
|
|
|
|
{
|
|
|
|
|
FString FrameString = Spacing == ScrubConstants::MinDisplayTickSpacing ? FString::Printf( TEXT("%.3f"), Seconds ) : FString::Printf( TEXT("%.2f"), Seconds );
|
|
|
|
|
|
|
|
|
|
// Space the text between the tick mark but slightly above
|
|
|
|
|
const TSharedRef< FSlateFontMeasure > FontMeasureService = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
|
|
|
|
|
FVector2D TextSize = FontMeasureService->Measure(FrameString, SmallLayoutFont);
|
|
|
|
|
FVector2D TextOffset( XPos-(TextSize.X*0.5f), InArgs.bMirrorLabels ? TextSize.Y : FMath::Abs( InArgs.AllottedGeometry.Size.Y - (InArgs.MajorTickHeight+TextSize.Y) ) );
|
|
|
|
|
|
|
|
|
|
FSlateDrawElement::MakeText(
|
|
|
|
|
OutDrawElements,
|
|
|
|
|
InArgs.StartLayer+1,
|
|
|
|
|
InArgs.AllottedGeometry.ToPaintGeometry( TextOffset, TextSize ),
|
|
|
|
|
FrameString,
|
|
|
|
|
SmallLayoutFont,
|
|
|
|
|
InArgs.ClippingRect,
|
|
|
|
|
InArgs.DrawEffects,
|
|
|
|
|
InArgs.TickColor
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if( !InArgs.bOnlyDrawMajorTicks )
|
|
|
|
|
{
|
|
|
|
|
// 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 = AbsOffsetNum % HalfDivider == 0 ? 7.0f : 4.0f;
|
|
|
|
|
|
|
|
|
|
FVector2D Offset(XPos, InArgs.bMirrorLabels ? 0.0f : FMath::Abs( InArgs.AllottedGeometry.Size.Y - MinorTickHeight ) );
|
2015-02-16 11:59:08 -05:00
|
|
|
FVector2D TickSize(0.0f, MinorTickHeight);
|
2014-03-14 14:13:41 -04:00
|
|
|
|
2015-02-16 11:59:08 -05:00
|
|
|
LinePoints[0] = FVector2D(0.0f,1.0f);
|
2014-03-14 14:13:41 -04:00
|
|
|
LinePoints[1] = TickSize;
|
|
|
|
|
|
|
|
|
|
const bool bAntiAlias = false;
|
|
|
|
|
// Draw each sub mark
|
|
|
|
|
FSlateDrawElement::MakeLines(
|
|
|
|
|
OutDrawElements,
|
|
|
|
|
InArgs.StartLayer,
|
|
|
|
|
InArgs.AllottedGeometry.ToPaintGeometry( Offset, TickSize ),
|
|
|
|
|
LinePoints,
|
|
|
|
|
InArgs.ClippingRect,
|
|
|
|
|
InArgs.DrawEffects,
|
|
|
|
|
InArgs.TickColor,
|
|
|
|
|
bAntiAlias
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
// Advance to next tick mark
|
|
|
|
|
++OffsetNum;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int32 FSequencerTimeSliderController::OnPaintTimeSlider( bool bMirrorLabels, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const
|
|
|
|
|
{
|
|
|
|
|
const bool bEnabled = bParentEnabled;
|
|
|
|
|
const ESlateDrawEffect::Type DrawEffects = bEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect;
|
|
|
|
|
|
|
|
|
|
TRange<float> LocalViewRange = TimeSliderArgs.ViewRange.Get();
|
|
|
|
|
const float LocalViewRangeMin = LocalViewRange.GetLowerBoundValue();
|
|
|
|
|
const float LocalViewRangeMax = LocalViewRange.GetUpperBoundValue();
|
|
|
|
|
const float LocalSequenceLength = LocalViewRangeMax-LocalViewRangeMin;
|
|
|
|
|
|
|
|
|
|
FVector2D Scale = FVector2D(1.0f,1.0f);
|
|
|
|
|
if ( LocalSequenceLength > 0)
|
|
|
|
|
{
|
|
|
|
|
FScrubRangeToScreen RangeToScreen( LocalViewRange, AllottedGeometry.Size );
|
|
|
|
|
|
|
|
|
|
const float MajorTickHeight = 9.0f;
|
|
|
|
|
|
|
|
|
|
FDrawTickArgs Args;
|
|
|
|
|
Args.AllottedGeometry = AllottedGeometry;
|
|
|
|
|
Args.bMirrorLabels = bMirrorLabels;
|
|
|
|
|
Args.bOnlyDrawMajorTicks = false;
|
|
|
|
|
Args.TickColor = FLinearColor::White;
|
|
|
|
|
Args.ClippingRect = MyClippingRect;
|
|
|
|
|
Args.DrawEffects = DrawEffects;
|
|
|
|
|
Args.StartLayer = LayerId;
|
|
|
|
|
Args.TickOffset = bMirrorLabels ? 0.0f : FMath::Abs( AllottedGeometry.Size.Y - MajorTickHeight );
|
|
|
|
|
Args.MajorTickHeight = MajorTickHeight;
|
|
|
|
|
|
|
|
|
|
DrawTicks( OutDrawElements, RangeToScreen, Args );
|
|
|
|
|
|
|
|
|
|
const float HandleSize = 13.0f;
|
2015-02-16 11:59:08 -05:00
|
|
|
float HalfSize = FMath::CeilToFloat(HandleSize/2.0f);
|
2014-03-14 14:13:41 -04:00
|
|
|
|
|
|
|
|
// Draw the scrub handle
|
|
|
|
|
const float XPos = RangeToScreen.InputToLocalX( TimeSliderArgs.ScrubPosition.Get() );
|
|
|
|
|
|
|
|
|
|
// Should draw above the text
|
|
|
|
|
const int32 ArrowLayer = LayerId + 2;
|
|
|
|
|
FPaintGeometry MyGeometry = AllottedGeometry.ToPaintGeometry( FVector2D( XPos-HalfSize, 0 ), FVector2D( HandleSize, AllottedGeometry.Size.Y ) );
|
|
|
|
|
FLinearColor ScrubColor = InWidgetStyle.GetColorAndOpacityTint();
|
|
|
|
|
|
|
|
|
|
// @todo Sequencer this color should be specified in the style
|
|
|
|
|
ScrubColor.A = ScrubColor.A*0.5f;
|
|
|
|
|
ScrubColor.B *= 0.1f;
|
|
|
|
|
ScrubColor.G *= 0.2f;
|
|
|
|
|
FSlateDrawElement::MakeBox(
|
|
|
|
|
OutDrawElements,
|
|
|
|
|
ArrowLayer,
|
|
|
|
|
MyGeometry,
|
|
|
|
|
bMirrorLabels ? ScrubHandleUp : ScrubHandleDown,
|
|
|
|
|
MyClippingRect,
|
|
|
|
|
DrawEffects,
|
|
|
|
|
ScrubColor
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return ArrowLayer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return LayerId;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FReply FSequencerTimeSliderController::OnMouseButtonDown( TSharedRef<SWidget> WidgetOwner, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
|
|
|
|
|
{
|
|
|
|
|
bool bHandleLeftMouseButton = MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton;
|
|
|
|
|
bool bHandleRightMouseButton = MouseEvent.GetEffectingButton() == EKeys::RightMouseButton && TimeSliderArgs.AllowZoom;
|
|
|
|
|
|
|
|
|
|
DistanceDragged = 0;
|
|
|
|
|
|
|
|
|
|
if ( bHandleLeftMouseButton )
|
|
|
|
|
{
|
|
|
|
|
return FReply::Handled().CaptureMouse( WidgetOwner ).PreventThrottling();
|
|
|
|
|
}
|
|
|
|
|
else if ( bHandleRightMouseButton )
|
|
|
|
|
{
|
|
|
|
|
// Always capture mouse if we left or right click on the widget
|
|
|
|
|
return FReply::Handled().CaptureMouse( WidgetOwner );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return FReply::Unhandled();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FReply FSequencerTimeSliderController::OnMouseButtonUp( TSharedRef<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 ;
|
|
|
|
|
|
|
|
|
|
if ( bHandleRightMouseButton )
|
|
|
|
|
{
|
|
|
|
|
if (!bPanning)
|
|
|
|
|
{
|
|
|
|
|
// return unhandled in case our parent wants to use our right mouse button to open a context menu
|
|
|
|
|
return FReply::Unhandled().ReleaseMouseCapture();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bPanning = false;
|
|
|
|
|
|
|
|
|
|
return FReply::Handled().ReleaseMouseCapture();
|
|
|
|
|
}
|
|
|
|
|
else if ( bHandleLeftMouseButton )
|
|
|
|
|
{
|
|
|
|
|
if( bDraggingScrubber )
|
|
|
|
|
{
|
|
|
|
|
TimeSliderArgs.OnEndScrubberMovement.ExecuteIfBound();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
FScrubRangeToScreen RangeToScreen( TimeSliderArgs.ViewRange.Get(), MyGeometry.Size );
|
|
|
|
|
FVector2D CursorPos = MyGeometry.AbsoluteToLocal(MouseEvent.GetLastScreenSpacePosition());
|
|
|
|
|
float NewValue = RangeToScreen.LocalXToInput(CursorPos.X);
|
|
|
|
|
|
2015-07-01 06:06:21 -04:00
|
|
|
if ( TimeSliderArgs.Settings->GetIsSnapEnabled() && TimeSliderArgs.Settings->GetSnapPlayTimeToInterval() )
|
2014-12-05 14:03:51 -05:00
|
|
|
{
|
2015-07-01 06:06:21 -04:00
|
|
|
NewValue = TimeSliderArgs.Settings->SnapTimeToInterval( NewValue );
|
2014-12-05 14:03:51 -05:00
|
|
|
}
|
|
|
|
|
|
2014-03-14 14:13:41 -04:00
|
|
|
CommitScrubPosition( NewValue, /*bIsScrubbing=*/false );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bDraggingScrubber = false;
|
|
|
|
|
return FReply::Handled().ReleaseMouseCapture();
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return FReply::Unhandled();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FReply FSequencerTimeSliderController::OnMouseMove( TSharedRef<SWidget> WidgetOwner, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
|
|
|
|
|
{
|
|
|
|
|
if ( WidgetOwner->HasMouseCapture() )
|
|
|
|
|
{
|
|
|
|
|
if (MouseEvent.IsMouseButtonDown( EKeys::RightMouseButton ))
|
|
|
|
|
{
|
|
|
|
|
if (!bPanning)
|
|
|
|
|
{
|
|
|
|
|
DistanceDragged += FMath::Abs( MouseEvent.GetCursorDelta().X );
|
---- Merging with SlateDev branch ----
Introduces the concept of "Active Ticking" to allow Slate to go to sleep when there is no need to update the UI.
While asleep, Slate will skip the Tick & Paint pass for that frame entirely.
- There are TWO ways to "wake" Slate and cause a Tick/Paint pass:
1. Provide some sort of input (mouse movement, clicks, and key presses). Slate will always tick when the user is active.
- Therefore, if the logic in a given widget's Tick is only relevant in response to user action, there is no need to register an active tick.
2. Register an Active Tick. Currently this is an all-or-nothing situation, so if a single active tick needs to execute, all of Slate will be ticked.
- The purpose of an Active Tick is to allow a widget to "drive" Slate and guarantee a Tick/Paint pass in the absence of any user action.
- Examples include animation, async operations that update periodically, progress updates, loading bars, etc.
- An empty active tick is registered for viewports when they are real-time, so game project widgets are unaffected by this change and should continue to work as before.
- An Active Tick is registered by creating an FWidgetActiveTickDelegate and passing it to SWidget::RegisterActiveTick()
- There are THREE ways to unregister an active tick:
1. Return EActiveTickReturnType::StopTicking from the active tick function
2. Pass the FActiveTickHandle returned by RegisterActiveTick() to SWidget::UnregisterActiveTick()
3. Destroy the widget responsible for the active tick
- Sleeping is currently disabled, can be enabled with Slate.AllowSlateToSleep cvar
- There is currently a little buffer time during which Slate continues to tick following any input. Long-term, this is planned to be removed.
- The duration of the buffer can be adjusted using Slate.SleepBufferPostInput cvar (defaults to 1.0f)
- The FCurveSequence API has been updated to work with the active tick system
- Playing a curve sequence now requires that you pass the widget being animated by the sequence
- The active tick will automatically be registered on behalf of the widget and unregister when the sequence is complete
- GetLerpLooping() has been removed. Instead, pass true as the second param to Play() to indicate that the animation will loop. This causes the active tick to be registered indefinitely until paused or jumped to the start/end.
[CL 2391669 by Dan Hertzka in Main branch]
2014-12-17 16:07:57 -05:00
|
|
|
if ( DistanceDragged > FSlateApplication::Get().GetDragTriggerDistance() )
|
2014-03-14 14:13:41 -04:00
|
|
|
{
|
|
|
|
|
bPanning = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
TRange<float> LocalViewRange = TimeSliderArgs.ViewRange.Get();
|
|
|
|
|
float LocalViewRangeMin = LocalViewRange.GetLowerBoundValue();
|
|
|
|
|
float LocalViewRangeMax = LocalViewRange.GetUpperBoundValue();
|
|
|
|
|
|
|
|
|
|
FScrubRangeToScreen ScaleInfo( LocalViewRange, MyGeometry.Size );
|
|
|
|
|
FVector2D ScreenDelta = MouseEvent.GetCursorDelta();
|
|
|
|
|
FVector2D InputDelta;
|
|
|
|
|
InputDelta.X = ScreenDelta.X/ScaleInfo.PixelsPerInput;
|
|
|
|
|
|
|
|
|
|
float NewViewOutputMin = LocalViewRangeMin - InputDelta.X;
|
|
|
|
|
float NewViewOutputMax = LocalViewRangeMax - InputDelta.X;
|
|
|
|
|
|
2015-06-12 08:55:27 -04:00
|
|
|
SetViewRange(NewViewOutputMin, NewViewOutputMax, EViewRangeInterpolation::Immediate);
|
2014-03-14 14:13:41 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (MouseEvent.IsMouseButtonDown( EKeys::LeftMouseButton ))
|
|
|
|
|
{
|
|
|
|
|
if ( !bDraggingScrubber )
|
|
|
|
|
{
|
|
|
|
|
DistanceDragged += FMath::Abs( MouseEvent.GetCursorDelta().X );
|
---- Merging with SlateDev branch ----
Introduces the concept of "Active Ticking" to allow Slate to go to sleep when there is no need to update the UI.
While asleep, Slate will skip the Tick & Paint pass for that frame entirely.
- There are TWO ways to "wake" Slate and cause a Tick/Paint pass:
1. Provide some sort of input (mouse movement, clicks, and key presses). Slate will always tick when the user is active.
- Therefore, if the logic in a given widget's Tick is only relevant in response to user action, there is no need to register an active tick.
2. Register an Active Tick. Currently this is an all-or-nothing situation, so if a single active tick needs to execute, all of Slate will be ticked.
- The purpose of an Active Tick is to allow a widget to "drive" Slate and guarantee a Tick/Paint pass in the absence of any user action.
- Examples include animation, async operations that update periodically, progress updates, loading bars, etc.
- An empty active tick is registered for viewports when they are real-time, so game project widgets are unaffected by this change and should continue to work as before.
- An Active Tick is registered by creating an FWidgetActiveTickDelegate and passing it to SWidget::RegisterActiveTick()
- There are THREE ways to unregister an active tick:
1. Return EActiveTickReturnType::StopTicking from the active tick function
2. Pass the FActiveTickHandle returned by RegisterActiveTick() to SWidget::UnregisterActiveTick()
3. Destroy the widget responsible for the active tick
- Sleeping is currently disabled, can be enabled with Slate.AllowSlateToSleep cvar
- There is currently a little buffer time during which Slate continues to tick following any input. Long-term, this is planned to be removed.
- The duration of the buffer can be adjusted using Slate.SleepBufferPostInput cvar (defaults to 1.0f)
- The FCurveSequence API has been updated to work with the active tick system
- Playing a curve sequence now requires that you pass the widget being animated by the sequence
- The active tick will automatically be registered on behalf of the widget and unregister when the sequence is complete
- GetLerpLooping() has been removed. Instead, pass true as the second param to Play() to indicate that the animation will loop. This causes the active tick to be registered indefinitely until paused or jumped to the start/end.
[CL 2391669 by Dan Hertzka in Main branch]
2014-12-17 16:07:57 -05:00
|
|
|
if ( DistanceDragged > FSlateApplication::Get().GetDragTriggerDistance() )
|
2014-03-14 14:13:41 -04:00
|
|
|
{
|
|
|
|
|
bDraggingScrubber = true;
|
|
|
|
|
TimeSliderArgs.OnBeginScrubberMovement.ExecuteIfBound();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
FScrubRangeToScreen RangeToScreen( TimeSliderArgs.ViewRange.Get(), MyGeometry.Size );
|
|
|
|
|
FVector2D CursorPos = MyGeometry.AbsoluteToLocal( MouseEvent.GetLastScreenSpacePosition() );
|
|
|
|
|
float NewValue = RangeToScreen.LocalXToInput( CursorPos.X );
|
|
|
|
|
|
2015-07-01 06:06:21 -04:00
|
|
|
if ( TimeSliderArgs.Settings->GetIsSnapEnabled() && TimeSliderArgs.Settings->GetSnapPlayTimeToInterval() )
|
2014-12-05 14:03:51 -05:00
|
|
|
{
|
2015-07-01 06:06:21 -04:00
|
|
|
NewValue = TimeSliderArgs.Settings->SnapTimeToInterval(NewValue);
|
2014-12-05 14:03:51 -05:00
|
|
|
}
|
|
|
|
|
|
2014-03-14 14:13:41 -04:00
|
|
|
CommitScrubPosition( NewValue, /*bIsScrubbing=*/true );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return FReply::Handled();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return FReply::Unhandled();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FSequencerTimeSliderController::CommitScrubPosition( float NewValue, bool bIsScrubbing )
|
|
|
|
|
{
|
|
|
|
|
// Manage the scrub position ourselves if its not bound to a delegate
|
|
|
|
|
if ( !TimeSliderArgs.ScrubPosition.IsBound() )
|
|
|
|
|
{
|
|
|
|
|
TimeSliderArgs.ScrubPosition.Set( NewValue );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TimeSliderArgs.OnScrubPositionChanged.ExecuteIfBound( NewValue, bIsScrubbing );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FReply FSequencerTimeSliderController::OnMouseWheel( TSharedRef<SWidget> WidgetOwner, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
|
|
|
|
|
{
|
2015-06-12 08:55:27 -04:00
|
|
|
TOptional<TRange<float>> NewTargetRange;
|
|
|
|
|
|
|
|
|
|
float MouseFractionX = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition()).X / MyGeometry.GetLocalSize().X;
|
|
|
|
|
if ( TimeSliderArgs.AllowZoom && (MouseEvent.IsLeftControlDown() || MouseEvent.IsRightControlDown()) )
|
2014-03-14 14:13:41 -04:00
|
|
|
{
|
2015-06-12 08:55:27 -04:00
|
|
|
const float ZoomDelta = -0.2f * MouseEvent.GetWheelDelta();
|
|
|
|
|
if (ZoomByDelta(ZoomDelta, MouseFractionX))
|
2014-03-14 14:13:41 -04:00
|
|
|
{
|
2015-06-12 08:55:27 -04:00
|
|
|
return FReply::Handled();
|
2014-03-14 14:13:41 -04:00
|
|
|
}
|
2015-06-12 08:55:27 -04:00
|
|
|
}
|
|
|
|
|
else if (MouseEvent.IsLeftShiftDown() || MouseEvent.IsRightShiftDown())
|
|
|
|
|
{
|
|
|
|
|
PanByDelta(-MouseEvent.GetWheelDelta());
|
2014-03-14 14:13:41 -04:00
|
|
|
return FReply::Handled();
|
|
|
|
|
}
|
2015-06-12 08:55:27 -04:00
|
|
|
|
2014-03-14 14:13:41 -04:00
|
|
|
return FReply::Unhandled();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int32 FSequencerTimeSliderController::OnPaintSectionView( const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, bool bEnabled, bool bDisplayTickLines, bool bDisplayScrubPosition ) const
|
|
|
|
|
{
|
|
|
|
|
const ESlateDrawEffect::Type DrawEffects = bEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect;
|
|
|
|
|
|
|
|
|
|
TRange<float> LocalViewRange = TimeSliderArgs.ViewRange.Get();
|
|
|
|
|
float LocalScrubPosition = TimeSliderArgs.ScrubPosition.Get();
|
|
|
|
|
|
|
|
|
|
float ViewRange = LocalViewRange.Size<float>();
|
|
|
|
|
float PixelsPerInput = ViewRange > 0 ? AllottedGeometry.Size.X / ViewRange : 0;
|
|
|
|
|
float LinePos = (LocalScrubPosition - LocalViewRange.GetLowerBoundValue()) * PixelsPerInput;
|
|
|
|
|
|
|
|
|
|
FScrubRangeToScreen RangeToScreen( LocalViewRange, AllottedGeometry.Size );
|
|
|
|
|
|
|
|
|
|
if( bDisplayTickLines )
|
|
|
|
|
{
|
|
|
|
|
// Draw major tick lines in the section area
|
|
|
|
|
FDrawTickArgs Args;
|
|
|
|
|
Args.AllottedGeometry = AllottedGeometry;
|
|
|
|
|
Args.bMirrorLabels = false;
|
|
|
|
|
Args.bOnlyDrawMajorTicks = true;
|
|
|
|
|
Args.TickColor = FLinearColor( 0.3f, 0.3f, 0.3f, 0.3f );
|
|
|
|
|
Args.ClippingRect = MyClippingRect;
|
|
|
|
|
Args.DrawEffects = DrawEffects;
|
|
|
|
|
// Draw major ticks under sections
|
|
|
|
|
Args.StartLayer = LayerId-1;
|
|
|
|
|
// Draw the tick the entire height of the section area
|
|
|
|
|
Args.TickOffset = 0.0f;
|
|
|
|
|
Args.MajorTickHeight = AllottedGeometry.Size.Y;
|
|
|
|
|
|
|
|
|
|
DrawTicks( OutDrawElements, RangeToScreen, Args );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if( bDisplayScrubPosition )
|
|
|
|
|
{
|
|
|
|
|
// Draw a line for the scrub position
|
|
|
|
|
TArray<FVector2D> LinePoints;
|
|
|
|
|
LinePoints.AddUninitialized(2);
|
2015-02-16 11:59:08 -05:00
|
|
|
LinePoints[0] = FVector2D( 0.0f, 0.0f );
|
|
|
|
|
LinePoints[1] = FVector2D( 0.0f, FMath::RoundToFloat( AllottedGeometry.Size.Y ) );
|
2014-03-14 14:13:41 -04:00
|
|
|
|
|
|
|
|
FSlateDrawElement::MakeLines(
|
|
|
|
|
OutDrawElements,
|
|
|
|
|
LayerId+1,
|
|
|
|
|
AllottedGeometry.ToPaintGeometry( FVector2D(LinePos, 0.0f ), FVector2D(1.0f,1.0f) ),
|
|
|
|
|
LinePoints,
|
|
|
|
|
MyClippingRect,
|
|
|
|
|
DrawEffects,
|
|
|
|
|
FLinearColor::White,
|
|
|
|
|
false
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return LayerId;
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-12 08:55:27 -04:00
|
|
|
void FSequencerTimeSliderController::SetViewRange( float NewRangeMin, float NewRangeMax, EViewRangeInterpolation Interpolation )
|
|
|
|
|
{
|
|
|
|
|
TOptional<float> LocalClampMin = TimeSliderArgs.ClampMin.Get();
|
|
|
|
|
TOptional<float> LocalClampMax = TimeSliderArgs.ClampMax.Get();
|
|
|
|
|
|
|
|
|
|
// Clamp the range if clamp values are set
|
|
|
|
|
if ( LocalClampMin.IsSet() && NewRangeMin < LocalClampMin.GetValue() )
|
|
|
|
|
{
|
|
|
|
|
NewRangeMin = LocalClampMin.GetValue();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( LocalClampMax.IsSet() && NewRangeMax > LocalClampMax.GetValue() )
|
|
|
|
|
{
|
|
|
|
|
NewRangeMax = LocalClampMax.GetValue();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const TRange<float> NewRange(NewRangeMin, NewRangeMax);
|
|
|
|
|
|
|
|
|
|
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 );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FSequencerTimeSliderController::ZoomByDelta( float InDelta, float MousePositionFraction )
|
|
|
|
|
{
|
|
|
|
|
TRange<float> LocalViewRange = TimeSliderArgs.ViewRange.Get().GetAnimationTarget();
|
|
|
|
|
float LocalViewRangeMax = LocalViewRange.GetUpperBoundValue();
|
|
|
|
|
float LocalViewRangeMin = LocalViewRange.GetLowerBoundValue();
|
|
|
|
|
const float OutputViewSize = LocalViewRangeMax - LocalViewRangeMin;
|
|
|
|
|
const float OutputChange = OutputViewSize * InDelta;
|
|
|
|
|
|
|
|
|
|
float NewViewOutputMin = LocalViewRangeMin - (OutputChange * MousePositionFraction);
|
|
|
|
|
float NewViewOutputMax = LocalViewRangeMax + (OutputChange * (1.f - MousePositionFraction));
|
|
|
|
|
|
|
|
|
|
if( FMath::Abs( OutputChange ) > 0.01f && NewViewOutputMin < NewViewOutputMax )
|
|
|
|
|
{
|
|
|
|
|
SetViewRange(NewViewOutputMin, NewViewOutputMax, EViewRangeInterpolation::Animated);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FSequencerTimeSliderController::PanByDelta( float InDelta )
|
|
|
|
|
{
|
|
|
|
|
TRange<float> LocalViewRange = TimeSliderArgs.ViewRange.Get().GetAnimationTarget();
|
|
|
|
|
|
|
|
|
|
float CurrentMin = LocalViewRange.GetLowerBoundValue();
|
|
|
|
|
float CurrentMax = LocalViewRange.GetUpperBoundValue();
|
|
|
|
|
|
|
|
|
|
// Adjust the delta to be a percentage of the current range
|
|
|
|
|
InDelta *= ScrubConstants::ScrollPanFraction * (CurrentMax - CurrentMin);
|
|
|
|
|
|
|
|
|
|
SetViewRange(CurrentMin + InDelta, CurrentMax + InDelta, EViewRangeInterpolation::Animated);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2014-03-14 14:13:41 -04:00
|
|
|
#undef LOCTEXT_NAMESPACE
|