2022-05-25 10:39:33 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
# include "SequencerKeyRenderer.h"
# include "Styling/AppStyle.h"
# include "CommonMovieSceneTools.h"
# include "MovieSceneTimeHelpers.h"
# include "SequencerSectionPainter.h"
# include "Sequencer.h"
# include "MovieSceneSequence.h"
# include "MovieScene.h"
# include "ISequencerSection.h"
# include "SequencerHotspots.h"
# include "MVVM/Extensions/IHoveredExtension.h"
# include "MVVM/Extensions/IOutlinerExtension.h"
# include "MVVM/Extensions/LinkedOutlinerExtension.h"
# include "MVVM/ViewModels/SequencerEditorViewModel.h"
# include "MVVM/ViewModels/SequencerTrackAreaViewModel.h"
namespace UE
{
namespace Sequencer
{
FKeyRenderer : : FPaintStyle : : FPaintStyle ( const FWidgetStyle & InWidgetStyle )
{
static const FName SelectionColorName ( " SelectionColor " ) ;
static const FName HighlightBrushName ( " Sequencer.AnimationOutliner.DefaultBorder " ) ;
static const FName StripeOverlayBrushName ( " Sequencer.Section.StripeOverlay " ) ;
static const FName SelectedTrackTintBrushName ( " Sequencer.Section.SelectedTrackTint " ) ;
static const FName BackgroundTrackTintBrushName ( " Sequencer.Section.BackgroundTint " ) ;
SelectionColor = FAppStyle : : GetSlateColor ( SelectionColorName ) . GetColor ( InWidgetStyle ) ;
BackgroundTrackTintBrush = FAppStyle : : GetBrush ( BackgroundTrackTintBrushName ) ;
SelectedTrackTintBrush = FAppStyle : : GetBrush ( SelectedTrackTintBrushName ) ;
StripeOverlayBrush = FAppStyle : : GetBrush ( StripeOverlayBrushName ) ;
HighlightBrush = FAppStyle : : GetBrush ( HighlightBrushName ) ;
}
FKeyRenderer : : FCachedState : : FCachedState ( const FSequencerSectionPainter & InPainter , FSequencer * Sequencer )
{
const FTimeToPixel & TimeToPixelConverter = InPainter . GetTimeConverter ( ) ;
UMovieScene * MovieScene = Sequencer - > GetFocusedMovieSceneSequence ( ) - > GetMovieScene ( ) ;
// Gather keys for a region larger than the view range to ensure we draw keys that are only just offscreen.
// Compute visible range taking into account a half-frame offset for keys, plus half a key width for keys that are partially offscreen
TRange < FFrameNumber > SectionRange = InPainter . SectionModel - > GetRange ( ) ;
const double HalfKeyWidth = 0.5f * ( TimeToPixelConverter . PixelToSeconds ( SequencerSectionConstants : : KeySize . X ) - TimeToPixelConverter . PixelToSeconds ( 0 ) ) ;
TRange < double > VisibleRange = UE : : MovieScene : : DilateRange ( Sequencer - > GetViewRange ( ) , - HalfKeyWidth , HalfKeyWidth ) ;
TRange < FFrameNumber > ValidKeyRange = Sequencer - > GetSubSequenceRange ( ) . Get ( MovieScene - > GetPlaybackRange ( ) ) ;
ValidPlayRangeMin = UE : : MovieScene : : DiscreteInclusiveLower ( ValidKeyRange ) ;
ValidPlayRangeMax = UE : : MovieScene : : DiscreteExclusiveUpper ( ValidKeyRange ) ;
PaddedViewRange = TRange < double > : : Intersection ( SectionRange / MovieScene - > GetTickResolution ( ) , VisibleRange ) ;
SelectionSerial = Sequencer - > GetSelection ( ) . GetSerialNumber ( ) ;
SelectionPreviewHash = Sequencer - > GetSelectionPreview ( ) . GetSelectionHash ( ) ;
}
FKeyRenderer : : ECacheFlags FKeyRenderer : : FCachedState : : CompareTo ( const FCachedState & Other ) const
{
ECacheFlags Flags = ECacheFlags : : None ;
if ( ValidPlayRangeMin ! = Other . ValidPlayRangeMin | | ValidPlayRangeMax ! = Other . ValidPlayRangeMax )
{
// The valid key ranges for the data has changed
Flags | = ECacheFlags : : KeyStateChanged ;
}
if ( SelectionSerial ! = Other . SelectionSerial | | SelectionPreviewHash ! = Other . SelectionPreviewHash )
{
// Selection states have changed
Flags | = ECacheFlags : : KeyStateChanged ;
}
if ( PaddedViewRange ! = Other . PaddedViewRange )
{
Flags | = ECacheFlags : : ViewChanged ;
const double RangeSize = PaddedViewRange . Size < double > ( ) ;
const double OtherRangeSize = Other . PaddedViewRange . Size < double > ( ) ;
if ( ! FMath : : IsNearlyEqual ( RangeSize , OtherRangeSize , RangeSize * 0.001 ) )
{
Flags | = ECacheFlags : : ViewZoomed ;
}
}
return Flags ;
}
FKeyRenderer : : FKeyDrawBatch : : FKeyDrawBatch ( const FSectionLayoutElement & LayoutElement )
{
for ( TWeakPtr < FChannelModel > WeakChannel : LayoutElement . GetChannels ( ) )
{
if ( TSharedPtr < FChannelModel > Channel = WeakChannel . Pin ( ) )
{
KeyDrawInfo . Emplace ( Channel ) ;
}
}
}
void FKeyRenderer : : FCachedKeyDrawInformation : : DrawExtra ( FSequencerSectionPainter & Painter , const FGeometry & KeyGeometry ) const
{
CachedKeyPositions . GetChannel ( ) - > GetKeyArea ( ) - > DrawExtra ( Painter , KeyGeometry ) ;
}
FKeyRenderer : : ECacheFlags FKeyRenderer : : FCachedKeyDrawInformation : : UpdateViewIndependentData ( FFrameRate TickResolution )
{
const bool bDataChanged = CachedKeyPositions . Update ( TickResolution ) ;
return bDataChanged ? ECacheFlags : : DataChanged : ECacheFlags : : None ;
}
void FKeyRenderer : : FCachedKeyDrawInformation : : CacheViewDependentData ( const TRange < double > & VisibleRange , ECacheFlags CacheFlags )
{
if ( EnumHasAnyFlags ( CacheFlags , FKeyRenderer : : ECacheFlags : : DataChanged | FKeyRenderer : : ECacheFlags : : ViewChanged | FKeyRenderer : : ECacheFlags : : ViewZoomed ) )
{
TArrayView < const FFrameNumber > OldFramesInRange = FramesInRange ;
// Gather all the key handles in this view range
CachedKeyPositions . GetKeysInRange ( VisibleRange , & TimesInRange , & FramesInRange , & HandlesInRange ) ;
bool bDrawnKeys = false ;
if ( ! EnumHasAnyFlags ( CacheFlags , FKeyRenderer : : ECacheFlags : : DataChanged ) & & OldFramesInRange . Num ( ) ! = 0 & & FramesInRange . Num ( ) ! = 0 )
{
// Try and preserve draw params if possible
const int32 PreserveStart = Algo : : LowerBound ( OldFramesInRange , FramesInRange [ 0 ] ) ;
const int32 PreserveEnd = Algo : : UpperBound ( OldFramesInRange , FramesInRange . Last ( ) ) ;
const int32 PreserveNum = PreserveEnd - PreserveStart ;
if ( PreserveNum > 0 )
{
TArray < FKeyDrawParams > NewDrawParams ;
const int32 HeadNum = Algo : : LowerBound ( FramesInRange , OldFramesInRange [ PreserveStart ] ) ;
if ( HeadNum > 0 )
{
NewDrawParams . SetNum ( HeadNum ) ;
CachedKeyPositions . GetChannel ( ) - > GetKeyArea ( ) - > DrawKeys ( HandlesInRange . Slice ( 0 , HeadNum ) , NewDrawParams ) ;
}
NewDrawParams . Append ( DrawParams . GetData ( ) + PreserveStart , PreserveNum ) ;
const int32 TailStart = Algo : : LowerBound ( FramesInRange , OldFramesInRange [ PreserveEnd - 1 ] ) ;
const int32 TailNum = FramesInRange . Num ( ) - TailStart ;
if ( TailNum > 0 )
{
NewDrawParams . SetNum ( FramesInRange . Num ( ) ) ;
CachedKeyPositions . GetChannel ( ) - > GetKeyArea ( ) - > DrawKeys ( HandlesInRange . Slice ( TailStart , TailNum ) , TArrayView < FKeyDrawParams > ( NewDrawParams ) . Slice ( TailStart , TailNum ) ) ;
}
DrawParams = MoveTemp ( NewDrawParams ) ;
bDrawnKeys = true ;
}
}
if ( ! bDrawnKeys )
{
DrawParams . SetNum ( TimesInRange . Num ( ) ) ;
if ( TimesInRange . Num ( ) )
{
// Draw these keys
CachedKeyPositions . GetChannel ( ) - > GetKeyArea ( ) - > DrawKeys ( HandlesInRange , DrawParams ) ;
}
}
check ( DrawParams . Num ( ) = = TimesInRange . Num ( ) & & TimesInRange . Num ( ) = = HandlesInRange . Num ( ) ) ;
}
// Always reset the pointers to the current key that needs processing
PreserveToIndex = TimesInRange . Num ( ) ;
NextUnhandledIndex = 0 ;
}
FKeyRenderer : : ECacheFlags FKeyRenderer : : FKeyDrawBatch : : UpdateViewIndependentData ( FFrameRate TickResolution )
{
FKeyRenderer : : ECacheFlags CacheState = FKeyRenderer : : ECacheFlags : : None ;
for ( FCachedKeyDrawInformation & CachedKeyDrawInfo : KeyDrawInfo )
{
CacheState | = CachedKeyDrawInfo . UpdateViewIndependentData ( TickResolution ) ;
}
return CacheState ;
}
void FKeyRenderer : : FKeyDrawBatch : : UpdateViewDependentData ( FSequencer * Sequencer , const FSequencerSectionPainter & InPainter , const FKeyRenderer : : FCachedState & InCachedState , ECacheFlags CacheFlags )
{
if ( CacheFlags = = ECacheFlags : : None )
{
// Cache is still hot - nothing to do
return ;
}
// ------------------------------------------------------------------------------
// @todo: This function can still be pretty burdonsome for section layouts with
// large numbers of nested key areas. In general we do not see more than ~10 key areas
// but control rig sections can have many hundreds of key areas. More optimization
// efforts may be required here - for the most part efforts have focused on reducing
// the frequency of computation, rather than speeding up the algorithm or re-arranging
// the cached data to make this faster (ie combining all keys into a single array / grid)
// ------------------------------------------------------------------------------
FFrameRate TickResolution = Sequencer - > GetFocusedTickResolution ( ) ;
const FTimeToPixel & TimeToPixelConverter = InPainter . GetTimeConverter ( ) ;
TSharedPtr < FKeyHotspot > KeyHotspot = HotspotCast < FKeyHotspot > ( Sequencer - > GetViewModel ( ) - > GetTrackArea ( ) - > GetHotspot ( ) ) ;
TArrayView < const FSequencerSelectedKey > HoveredKeys ;
if ( KeyHotspot )
{
HoveredKeys = KeyHotspot - > Keys ;
}
const TSet < FSequencerSelectedKey > & SelectedKeys = Sequencer - > GetSelection ( ) . GetSelectedKeys ( ) ;
const TMap < FSequencerSelectedKey , ESelectionPreviewState > & SelectionPreview = Sequencer - > GetSelectionPreview ( ) . GetDefinedKeyStates ( ) ;
const bool bHasAnySelection = SelectedKeys . Num ( ) ! = 0 ;
const bool bHasAnySelectionPreview = SelectionPreview . Num ( ) ! = 0 ;
const bool bHasAnyHoveredKeys = HoveredKeys . Num ( ) ! = 0 ;
// ------------------------------------------------------------------------------
// Update view-dependent data for each draw info
for ( FCachedKeyDrawInformation & Info : KeyDrawInfo )
{
Info . CacheViewDependentData ( InCachedState . PaddedViewRange , CacheFlags ) ;
}
// ------------------------------------------------------------------------------
// If the data has changed, or key state has changed, or the view has been zoomed
// we cannot preserve any keys (because we don't know whether they are still valid)
const bool bCanPreserveKeys = ! EnumHasAnyFlags ( CacheFlags , FKeyRenderer : : ECacheFlags : : DataChanged | FKeyRenderer : : ECacheFlags : : ViewZoomed | FKeyRenderer : : ECacheFlags : : KeyStateChanged ) ;
FFrameNumber PreserveStartFrame = FFrameNumber ( TNumericLimits < int32 > : : Max ( ) ) ;
TArray < FKey > PreservedKeys ;
// Attempt to preserve any previously computed key draw information
if ( bCanPreserveKeys & & PrecomputedKeys . Num ( ) ! = 0 )
{
const FFrameNumber LowerBoundFrame = ( InCachedState . PaddedViewRange . GetLowerBoundValue ( ) * TickResolution ) . CeilToFrame ( ) ;
const FFrameNumber UpperBoundFrame = ( InCachedState . PaddedViewRange . GetUpperBoundValue ( ) * TickResolution ) . FloorToFrame ( ) ;
const int32 PreserveStartIndex = Algo : : LowerBoundBy ( PrecomputedKeys , LowerBoundFrame , & FKey : : KeyTickStart ) ;
const int32 PreserveEndIndex = Algo : : UpperBoundBy ( PrecomputedKeys , UpperBoundFrame , & FKey : : KeyTickEnd ) ;
const int32 PreserveNum = PreserveEndIndex - PreserveStartIndex ;
if ( PreserveNum > 0 )
{
PreservedKeys = TArray < FKey > ( PrecomputedKeys . GetData ( ) + PreserveStartIndex , PreserveNum ) ;
PreserveStartFrame = PreservedKeys [ 0 ] . KeyTickStart ;
FFrameNumber ActualPreserveEndFrame = PreservedKeys . Last ( ) . KeyTickEnd ;
for ( FCachedKeyDrawInformation & Info : KeyDrawInfo )
{
Info . PreserveToIndex = Algo : : UpperBound ( Info . FramesInRange , ActualPreserveEndFrame ) ;
}
}
}
// ------------------------------------------------------------------------------
// Begin precomputation of keys to draw
PrecomputedKeys . Reset ( ) ;
static float PixelOverlapThreshold = 3.f ;
const double TimeOverlapThreshold = TimeToPixelConverter . PixelToSeconds ( PixelOverlapThreshold ) - TimeToPixelConverter . PixelToSeconds ( 0.f ) ;
auto AnythingLeftToDraw = [ ] ( const FCachedKeyDrawInformation & In )
{
return In . NextUnhandledIndex < In . TimesInRange . Num ( ) ;
} ;
// Keep iterating all the cached key positions until we've moved through everything
// As stated above - this loop does not scale well for large numbers of KeyDrawInfo
// Which is generally not a problem, but is troublesome for Control Rigs
while ( KeyDrawInfo . ContainsByPredicate ( AnythingLeftToDraw ) )
{
// Determine the next key position to draw
FFrameNumber CardinalKeyFrame = FFrameNumber ( TNumericLimits < int32 > : : Max ( ) ) ;
for ( const FCachedKeyDrawInformation & Info : KeyDrawInfo )
{
if ( Info . NextUnhandledIndex < Info . TimesInRange . Num ( ) )
{
CardinalKeyFrame = FMath : : Min ( CardinalKeyFrame , Info . FramesInRange [ Info . NextUnhandledIndex ] ) ;
}
}
// If the cardinal time overlaps the preserved range, skip those keys
if ( CardinalKeyFrame > = PreserveStartFrame & & PreservedKeys . Num ( ) ! = 0 )
{
PrecomputedKeys . Append ( PreservedKeys ) ;
PreservedKeys . Empty ( ) ;
for ( FCachedKeyDrawInformation & Info : KeyDrawInfo )
{
Info . NextUnhandledIndex = Info . PreserveToIndex ;
}
continue ;
}
double CardinalKeyTime = CardinalKeyFrame / TickResolution ;
// Start grouping keys at the current key time plus 99% of the threshold to ensure that we group at the center of keys
// and that we avoid floating point precision issues where there is only one key [(KeyTime + TimeOverlapThreshold) - KeyTime != TimeOverlapThreshold] for some floats
CardinalKeyTime + = TimeOverlapThreshold * 0.9994f ;
// Track whether all of the keys are within the valid range
bool bIsInRange = true ;
const FFrameNumber ValidPlayRangeMin = InCachedState . ValidPlayRangeMin ;
const FFrameNumber ValidPlayRangeMax = InCachedState . ValidPlayRangeMax ;
double AverageKeyTime = 0.f ;
int32 NumKeyTimes = 0 ;
FFrameNumber KeyTickStart = FFrameNumber ( TNumericLimits < int32 > : : Max ( ) ) ;
FFrameNumber KeyTickEnd = FFrameNumber ( TNumericLimits < int32 > : : Lowest ( ) ) ;
auto HandleKey = [ & bIsInRange , & AverageKeyTime , & NumKeyTimes , & KeyTickStart , & KeyTickEnd , ValidPlayRangeMin , ValidPlayRangeMax ] ( FFrameNumber KeyFrame , double KeyTime )
{
if ( bIsInRange & & ( KeyFrame < ValidPlayRangeMin | | KeyFrame > = ValidPlayRangeMax ) )
{
bIsInRange = false ;
}
KeyTickStart = FMath : : Min ( KeyFrame , KeyTickStart ) ;
KeyTickEnd = FMath : : Max ( KeyFrame , KeyTickEnd ) ;
AverageKeyTime + = KeyTime ;
+ + NumKeyTimes ;
} ;
bool bFoundKey = false ;
FKey NewKey ;
int32 NumPreviewSelected = 0 ;
int32 NumPreviewNotSelected = 0 ;
int32 NumSelected = 0 ;
int32 NumHovered = 0 ;
int32 TotalNumKeys = 0 ;
int32 NumOverlaps = 0 ;
// Determine the ranges of keys considered to reside at this position
for ( int32 DrawIndex = 0 ; DrawIndex < KeyDrawInfo . Num ( ) ; + + DrawIndex )
{
FCachedKeyDrawInformation & Info = KeyDrawInfo [ DrawIndex ] ;
if ( Info . NextUnhandledIndex > = Info . TimesInRange . Num ( ) )
{
NewKey . Flags | = EKeyRenderingFlags : : PartialKey ;
continue ;
}
else if ( ! FMath : : IsNearlyEqual ( Info . TimesInRange [ Info . NextUnhandledIndex ] , CardinalKeyTime , TimeOverlapThreshold ) )
{
NewKey . Flags | = EKeyRenderingFlags : : PartialKey ;
continue ;
}
int32 ThisNumOverlaps = - 1 ;
do
{
HandleKey ( Info . FramesInRange [ Info . NextUnhandledIndex ] , Info . TimesInRange [ Info . NextUnhandledIndex ] ) ;
if ( ! bFoundKey )
{
NewKey . Params = Info . DrawParams [ Info . NextUnhandledIndex ] ;
bFoundKey = true ;
}
else if ( Info . DrawParams [ Info . NextUnhandledIndex ] ! = NewKey . Params )
{
NewKey . Flags | = EKeyRenderingFlags : : PartialKey ;
}
// Avoid creating FSequencerSelectedKeys unless absolutely necessary
FKeyHandle ThisKeyHandle = Info . HandlesInRange [ Info . NextUnhandledIndex ] ;
TSharedPtr < FChannelModel > ThisChannel = Info . CachedKeyPositions . GetChannel ( ) ;
if ( bHasAnySelection )
{
FSequencerSelectedKey TestKey ( * InPainter . SectionModel - > GetSection ( ) , ThisChannel , ThisKeyHandle ) ;
if ( SelectedKeys . Contains ( TestKey ) )
{
+ + NumSelected ;
}
}
if ( bHasAnySelectionPreview )
{
FSequencerSelectedKey TestKey ( * InPainter . SectionModel - > GetSection ( ) , ThisChannel , ThisKeyHandle ) ;
if ( const ESelectionPreviewState * SelectionPreviewState = SelectionPreview . Find ( TestKey ) )
{
NumPreviewSelected + = int32 ( * SelectionPreviewState = = ESelectionPreviewState : : Selected ) ;
NumPreviewNotSelected + = int32 ( * SelectionPreviewState = = ESelectionPreviewState : : NotSelected ) ;
}
}
if ( bHasAnyHoveredKeys )
{
FSequencerSelectedKey TestKey ( * InPainter . SectionModel - > GetSection ( ) , ThisChannel , ThisKeyHandle ) ;
NumHovered + = int32 ( HoveredKeys . Contains ( TestKey ) ) ;
}
+ + TotalNumKeys ;
+ + Info . NextUnhandledIndex ;
+ + ThisNumOverlaps ;
}
while ( Info . NextUnhandledIndex < Info . TimesInRange . Num ( ) & & FMath : : IsNearlyEqual ( Info . TimesInRange [ Info . NextUnhandledIndex ] , CardinalKeyTime , TimeOverlapThreshold ) ) ;
NumOverlaps + = ThisNumOverlaps ;
}
if ( NumKeyTimes = = 0 ) //-V547
{
// This is not actually possible since HandleKey must have been called
// at least once, but it needs to be here to avoid a static analysis warning
break ;
}
NewKey . FinalKeyPositionSeconds = AverageKeyTime / NumKeyTimes ;
NewKey . KeyTickStart = KeyTickStart ;
NewKey . KeyTickEnd = KeyTickEnd ;
if ( EnumHasAnyFlags ( NewKey . Flags , EKeyRenderingFlags : : PartialKey ) )
{
static const FSlateBrush * PartialKeyBrush = FAppStyle : : GetBrush ( " Sequencer.PartialKey " ) ;
NewKey . Params . FillBrush = NewKey . Params . BorderBrush = PartialKeyBrush ;
}
// Determine the key color based on its selection/hover states
if ( NumPreviewSelected = = TotalNumKeys )
{
NewKey . Flags | = EKeyRenderingFlags : : PreviewSelected ;
}
else if ( NumPreviewNotSelected = = TotalNumKeys )
{
NewKey . Flags | = EKeyRenderingFlags : : PreviewNotSelected ;
}
else if ( NumSelected = = TotalNumKeys )
{
NewKey . Flags | = EKeyRenderingFlags : : Selected ;
}
else if ( NumSelected ! = 0 )
{
NewKey . Flags | = EKeyRenderingFlags : : AnySelected ;
}
else if ( NumHovered = = TotalNumKeys )
{
NewKey . Flags | = EKeyRenderingFlags : : Hovered ;
}
if ( NumOverlaps > 0 )
{
NewKey . Flags | = EKeyRenderingFlags : : Overlaps ;
}
if ( ! bIsInRange )
{
NewKey . Flags | = EKeyRenderingFlags : : OutOfRange ;
}
PrecomputedKeys . Add ( NewKey ) ;
}
PrecomputedCurve . Reset ( ) ;
const FName FloatChannelTypeName = FMovieSceneFloatChannel : : StaticStruct ( ) - > GetFName ( ) ;
const FName DoubleChannelTypeName = FMovieSceneDoubleChannel : : StaticStruct ( ) - > GetFName ( ) ;
for ( FCachedKeyDrawInformation & Info : KeyDrawInfo )
{
const TSharedPtr < IKeyArea > & ThisKeyArea = Info . CachedKeyPositions . GetChannel ( ) - > GetKeyArea ( ) ;
const FMovieSceneChannelHandle & Channel = ThisKeyArea - > GetChannel ( ) ;
const FName ChannelTypeName = Channel . GetChannelTypeName ( ) ;
const FFrameNumber DeltaFrame = TimeToPixelConverter . PixelDeltaToFrame ( 1.f ) . FrameNumber ;
const FFrameNumber LowerBoundFrame = ( InCachedState . PaddedViewRange . GetLowerBoundValue ( ) * TickResolution ) . CeilToFrame ( ) ;
const FFrameNumber UpperBoundFrame = ( InCachedState . PaddedViewRange . GetUpperBoundValue ( ) * TickResolution ) . FloorToFrame ( ) ;
TOptional < float > PreviousValue ;
float MaxValue = - FLT_MAX , MinValue = FLT_MAX ;
if ( ChannelTypeName = = FloatChannelTypeName )
{
FMovieSceneFloatChannel * FloatChannel = ThisKeyArea - > GetChannel ( ) . Cast < FMovieSceneFloatChannel > ( ) . Get ( ) ;
if ( ! FloatChannel | | ! FloatChannel - > GetShowCurve ( ) )
{
continue ;
}
for ( FFrameNumber FrameNumber = LowerBoundFrame ; FrameNumber < = UpperBoundFrame ; )
{
double EvalTime = TickResolution . AsSeconds ( FrameNumber ) ;
float Value = 0.f ;
if ( FloatChannel - > Evaluate ( FrameNumber , Value ) )
{
if ( ( PreviousValue . IsSet ( ) & & ! FMath : : IsNearlyEqual ( Value , PreviousValue . GetValue ( ) ) ) | | FrameNumber = = LowerBoundFrame | | FrameNumber = = UpperBoundFrame )
{
MaxValue = FMath : : Max ( MaxValue , Value ) ;
MinValue = FMath : : Min ( MinValue , Value ) ;
FCurveKey NewKey ;
NewKey . Value = Value ;
NewKey . FinalKeyPositionSeconds = EvalTime ;
PrecomputedCurve . Add ( NewKey ) ;
}
if ( ! PreviousValue . IsSet ( ) )
{
PreviousValue = Value ;
}
}
if ( FrameNumber > = UpperBoundFrame )
{
break ;
}
FrameNumber + = DeltaFrame ;
FrameNumber = FMath : : Min ( FrameNumber , UpperBoundFrame ) ;
}
}
else if ( ChannelTypeName = = DoubleChannelTypeName )
{
FMovieSceneDoubleChannel * DoubleChannel = ThisKeyArea - > GetChannel ( ) . Cast < FMovieSceneDoubleChannel > ( ) . Get ( ) ;
if ( ! DoubleChannel | | ! DoubleChannel - > GetShowCurve ( ) )
{
continue ;
}
for ( FFrameNumber FrameNumber = LowerBoundFrame ; FrameNumber < = UpperBoundFrame ; )
{
double EvalTime = TickResolution . AsSeconds ( FrameNumber ) ;
// Displaying values in float in the Sequencer track view.
float Value = 0.f ;
if ( DoubleChannel - > Evaluate ( FrameNumber , Value ) )
{
if ( ( PreviousValue . IsSet ( ) & & ! FMath : : IsNearlyEqual ( Value , PreviousValue . GetValue ( ) ) ) | | FrameNumber = = LowerBoundFrame | | FrameNumber = = UpperBoundFrame )
{
MaxValue = FMath : : Max ( MaxValue , Value ) ;
MinValue = FMath : : Min ( MinValue , Value ) ;
FCurveKey NewKey ;
NewKey . Value = Value ;
NewKey . FinalKeyPositionSeconds = EvalTime ;
PrecomputedCurve . Add ( NewKey ) ;
}
if ( ! PreviousValue . IsSet ( ) )
{
PreviousValue = Value ;
}
}
if ( FrameNumber > = UpperBoundFrame )
{
break ;
}
FrameNumber + = DeltaFrame ;
FrameNumber = FMath : : Min ( FrameNumber , UpperBoundFrame ) ;
}
}
else
{
continue ;
}
TOptional < float > SpecifiedMinValue ;
TOptional < float > SpecifiedMaxValue ;
FString KeyAreaName = ThisKeyArea . Get ( ) - > GetName ( ) . ToString ( ) ;
if ( Sequencer - > GetSequencerSettings ( ) - > HasKeyAreaCurveExtents ( KeyAreaName ) )
{
float CurveMin = 0.f ;
float CurveMax = 0.f ;
Sequencer - > GetSequencerSettings ( ) - > GetKeyAreaCurveExtents ( KeyAreaName , CurveMin , CurveMax ) ;
SpecifiedMinValue = CurveMin ;
SpecifiedMaxValue = CurveMax ;
}
if ( SpecifiedMinValue . IsSet ( ) )
{
MinValue = SpecifiedMinValue . GetValue ( ) ;
}
if ( SpecifiedMaxValue . IsSet ( ) )
{
MaxValue = SpecifiedMaxValue . GetValue ( ) ;
}
// Normalize or clamp
if ( PrecomputedCurve . Num ( ) > 0 )
{
float DiffValue = MaxValue - MinValue ;
for ( FCurveKey & CurveKey : PrecomputedCurve )
{
if ( SpecifiedMaxValue . IsSet ( ) )
{
CurveKey . Value = FMath : : Min ( CurveKey . Value , SpecifiedMaxValue . GetValue ( ) ) ;
}
if ( SpecifiedMinValue . IsSet ( ) )
{
CurveKey . Value = FMath : : Max ( CurveKey . Value , SpecifiedMinValue . GetValue ( ) ) ;
}
CurveKey . Value = ( CurveKey . Value - MinValue ) / DiffValue ;
}
}
}
}
void FKeyRenderer : : FKeyDrawBatch : : DrawCurve ( FSequencer * Sequencer , FSequencerSectionPainter & Painter , const FGeometry & KeyGeometry , const FPaintStyle & Style , const FKeyRendererPaintArgs & Args ) const
{
const FTimeToPixel & TimeToPixelConverter = Painter . GetTimeConverter ( ) ;
TOptional < FSlateClippingState > PreviousClipState = Painter . DrawElements . GetClippingState ( ) ;
Painter . DrawElements . PopClip ( ) ;
const int32 KeyLayer = Painter . LayerId ;
const ESlateDrawEffect BaseDrawEffects = Painter . bParentEnabled ? ESlateDrawEffect : : None : ESlateDrawEffect : : DisabledEffect ;
TArray < FVector2D > CurvePoints ;
for ( const FCurveKey & CurveKey : PrecomputedCurve )
{
CurvePoints . Add (
FVector2D ( TimeToPixelConverter . SecondsToPixel ( CurveKey . FinalKeyPositionSeconds ) ,
( 1.0f - CurveKey . Value ) * KeyGeometry . GetLocalSize ( ) . Y ) ) ;
}
const float CurveThickness = 1.f ;
const bool bAntiAliasCurves = true ;
const FLinearColor CurveColor ( 0.8f , 0.8f , 0.f , 0.7 ) ;
FSlateDrawElement : : MakeLines (
Painter . DrawElements ,
KeyLayer ,
KeyGeometry . ToPaintGeometry ( ) ,
CurvePoints ,
BaseDrawEffects ,
CurveColor ,
bAntiAliasCurves ,
CurveThickness
) ;
Painter . LayerId = KeyLayer + 2 ;
for ( const FCachedKeyDrawInformation & CachedKeyDrawInfo : KeyDrawInfo )
{
CachedKeyDrawInfo . DrawExtra ( Painter , KeyGeometry ) ;
}
Painter . DrawElements . GetClippingManager ( ) . PushClippingState ( PreviousClipState . GetValue ( ) ) ;
}
void
FKeyRenderer : : FKeyDrawBatch : : Draw ( FSequencer * Sequencer , FSequencerSectionPainter & Painter , const FGeometry & KeyGeometry , const FPaintStyle & Style , const FKeyRendererPaintArgs & Args ) const
{
const FTimeToPixel & TimeToPixelConverter = Painter . GetTimeConverter ( ) ;
TOptional < FSlateClippingState > PreviousClipState = Painter . DrawElements . GetClippingState ( ) ;
Painter . DrawElements . PopClip ( ) ;
const int32 KeyLayer = Painter . LayerId ;
const ESlateDrawEffect BaseDrawEffects = Painter . bParentEnabled ? ESlateDrawEffect : : None : ESlateDrawEffect : : DisabledEffect ;
for ( const FKey & Key : PrecomputedKeys )
{
FKeyDrawParams Params = Key . Params ;
if ( EnumHasAnyFlags ( Key . Flags , EKeyRenderingFlags : : PartialKey ) )
{
Params . FillOffset = FVector2D ( 0.f , 0.f ) ;
Params . FillTint = Params . BorderTint = FLinearColor : : White ;
}
const bool bSelected = EnumHasAnyFlags ( Key . Flags , EKeyRenderingFlags : : Selected ) ;
// Determine the key color based on its selection/hover states
if ( EnumHasAnyFlags ( Key . Flags , EKeyRenderingFlags : : PreviewSelected ) )
{
FLinearColor PreviewSelectionColor = Style . SelectionColor . LinearRGBToHSV ( ) ;
PreviewSelectionColor . R + = 0.1f ; // +10% hue
PreviewSelectionColor . G = 0.6f ; // 60% saturation
Params . BorderTint = Params . FillTint = PreviewSelectionColor . HSVToLinearRGB ( ) ;
}
else if ( EnumHasAnyFlags ( Key . Flags , EKeyRenderingFlags : : PreviewNotSelected ) )
{
Params . BorderTint = FLinearColor ( 0.05f , 0.05f , 0.05f , 1.0f ) ;
}
else if ( bSelected )
{
Params . BorderTint = Style . SelectionColor ;
Params . FillTint = FLinearColor ( 0.05f , 0.05f , 0.05f , 1.0f ) ;
}
else if ( EnumHasAnyFlags ( Key . Flags , EKeyRenderingFlags : : AnySelected ) )
{
// partially selected
Params . BorderTint = Style . SelectionColor . CopyWithNewOpacity ( 0.5f ) ;
Params . FillTint = FLinearColor ( 0.05f , 0.05f , 0.05f , 0.5f ) ;
}
else if ( EnumHasAnyFlags ( Key . Flags , EKeyRenderingFlags : : Hovered ) )
{
Params . BorderTint = FLinearColor ( 1.0f , 1.0f , 1.0f , 1.0f ) ;
Params . FillTint = FLinearColor ( 1.0f , 1.0f , 1.0f , 1.0f ) ;
}
else
{
Params . BorderTint = FLinearColor ( 0.05f , 0.05f , 0.05f , 1.0f ) ;
}
// Color keys with overlaps with a red border
if ( EnumHasAnyFlags ( Key . Flags , EKeyRenderingFlags : : Overlaps ) )
{
Params . BorderTint = FLinearColor ( 0.83f , 0.12f , 0.12f , 1.0f ) ; // Red
}
const ESlateDrawEffect KeyDrawEffects = EnumHasAnyFlags ( Key . Flags , EKeyRenderingFlags : : OutOfRange ) ? ESlateDrawEffect : : DisabledEffect : BaseDrawEffects ;
// draw border
const FVector2D KeySize = bSelected ? SequencerSectionConstants : : KeySize + Args . ThrobAmount * Args . KeyThrobValue : SequencerSectionConstants : : KeySize ;
static const float BrushBorderWidth = 2.0f ;
const float KeyPositionPx = TimeToPixelConverter . SecondsToPixel ( Key . FinalKeyPositionSeconds ) ;
FSlateDrawElement : : MakeBox (
Painter . DrawElements ,
// always draw selected keys on top of other keys
bSelected ? KeyLayer + 1 : KeyLayer ,
// Center the key along Y. Ensure the middle of the key is at the actual key time
KeyGeometry . ToPaintGeometry (
FVector2D (
KeyPositionPx - FMath : : CeilToFloat ( KeySize . X / 2.0f ) ,
( ( KeyGeometry . GetLocalSize ( ) . Y / 2.0f ) - ( KeySize . Y / 2.0f ) )
) ,
KeySize
) ,
Params . BorderBrush ,
KeyDrawEffects ,
Params . BorderTint
) ;
// draw fill
FSlateDrawElement : : MakeBox (
Painter . DrawElements ,
// always draw selected keys on top of other keys
bSelected ? KeyLayer + 2 : KeyLayer + 1 ,
// Center the key along Y. Ensure the middle of the key is at the actual key time
KeyGeometry . ToPaintGeometry (
Params . FillOffset +
FVector2D (
( KeyPositionPx - FMath : : CeilToFloat ( ( KeySize . X / 2.0f ) - BrushBorderWidth ) ) ,
( ( KeyGeometry . GetLocalSize ( ) . Y / 2.0f ) - ( ( KeySize . Y / 2.0f ) - BrushBorderWidth ) )
) ,
KeySize - 2.0f * BrushBorderWidth
) ,
Params . FillBrush ,
KeyDrawEffects ,
Params . FillTint
) ;
}
Painter . LayerId = KeyLayer + 2 ;
Painter . DrawElements . GetClippingManager ( ) . PushClippingState ( PreviousClipState . GetValue ( ) ) ;
}
void FKeyRenderer : : DrawLayoutElement ( FSequencer * Sequencer , const FSequencerSectionPainter & SectionPainter , const FSectionLayoutElement & LayoutElement , const FPaintStyle & Style , const FKeyRendererPaintArgs & Args ) const
{
FGeometry KeyAreaGeometry = LayoutElement . ComputeGeometry ( SectionPainter . SectionGeometry ) ;
TArrayView < const TWeakPtr < FChannelModel > > WeakChannels = LayoutElement . GetChannels ( ) ;
TOptional < FLinearColor > ChannelColor ;
if ( WeakChannels . Num ( ) = = 1 & & Sequencer - > GetSequencerSettings ( ) - > GetShowChannelColors ( ) )
{
ChannelColor = WeakChannels [ 0 ] . Pin ( ) - > GetKeyArea ( ) - > GetColor ( ) ;
}
FSequencerSelection & Selection = Sequencer - > GetSelection ( ) ;
const ESlateDrawEffect DrawEffects = SectionPainter . bParentEnabled
? ESlateDrawEffect : : None
: ESlateDrawEffect : : DisabledEffect ;
// --------------------------------------------
// Draw the channel strip if necessary
if ( ChannelColor . IsSet ( ) )
{
static float BoxThickness = 5.f ;
static const FSlateBrush * const StripeOverlayBrush = FAppStyle : : GetBrush ( " Sequencer.Section.StripeOverlay " ) ;
FVector2D KeyAreaSize = KeyAreaGeometry . GetLocalSize ( ) ;
FSlateDrawElement : : MakeBox (
SectionPainter . DrawElements ,
SectionPainter . LayerId ,
KeyAreaGeometry . ToPaintGeometry ( FVector2D ( KeyAreaSize . X , BoxThickness ) , FSlateLayoutTransform ( FVector2D ( 0.f , KeyAreaSize . Y * .5f - BoxThickness * .5f ) ) ) ,
StripeOverlayBrush ,
DrawEffects ,
ChannelColor . GetValue ( )
) ;
}
FLinkedOutlinerExtension * LinkedOutlinerItem = LayoutElement . GetModel ( ) ? LayoutElement . GetModel ( ) - > CastThis < FLinkedOutlinerExtension > ( ) : nullptr ;
TSharedPtr < FViewModel > Model = LinkedOutlinerItem ? LinkedOutlinerItem - > GetLinkedOutlinerItem ( ) . AsModel ( ) : LayoutElement . GetModel ( ) ;
if ( Model )
{
FLinearColor HighlightColor ;
bool bDrawHighlight = false ;
if ( Sequencer - > GetSelection ( ) . NodeHasSelectedKeysOrSections ( Model ) )
{
bDrawHighlight = true ;
HighlightColor = FLinearColor ( 1.0f , 1.0f , 1.0f , 0.15f ) ;
}
else if ( Model - > CastThis < IHoveredExtension > ( ) )
{
bDrawHighlight = true ;
HighlightColor = FLinearColor ( 1.0f , 1.0f , 1.0f , 0.05f ) ;
}
// --------------------------------------------
// Draw hover or selection highlight
if ( bDrawHighlight )
{
FSlateDrawElement : : MakeBox (
SectionPainter . DrawElements ,
SectionPainter . LayerId ,
KeyAreaGeometry . ToPaintGeometry ( ) ,
Style . HighlightBrush ,
DrawEffects ,
HighlightColor
) ;
}
// --------------------------------------------
// Draw display node selection tint
if ( Selection . IsSelected ( Model ) )
{
FSlateDrawElement : : MakeBox (
SectionPainter . DrawElements ,
SectionPainter . LayerId ,
KeyAreaGeometry . ToPaintGeometry ( ) ,
Style . SelectedTrackTintBrush ,
DrawEffects ,
Style . SelectionColor
) ;
}
}
// --------------------------------------------
// Draw section selection tint
const bool bSectionSelected = Selection . IsSelected ( SectionPainter . SectionModel ) ;
if ( bSectionSelected & & Args . SectionThrobValue ! = 0.f )
{
FSlateDrawElement : : MakeBox (
SectionPainter . DrawElements ,
SectionPainter . LayerId ,
KeyAreaGeometry . ToPaintGeometry ( ) ,
Style . BackgroundTrackTintBrush ,
DrawEffects ,
Style . SelectionColor . CopyWithNewOpacity ( Args . SectionThrobValue )
) ;
}
}
void FKeyRenderer : : UpdateKeyLayouts ( FSequencer * Sequencer , const FSequencerSectionPainter & InPainter , const FSectionLayout & InSectionLayout ) const
{
// Update the cache
FCachedState NewCachedState ( InPainter , Sequencer ) ;
ECacheFlags CacheFlags = ECacheFlags : : All ;
if ( CachedState . IsSet ( ) )
{
CacheFlags = CachedState - > CompareTo ( NewCachedState ) ;
}
CachedState = NewCachedState ;
if ( CachedState - > PaddedViewRange . IsEmpty ( ) )
{
CachedKeyLayouts . Reset ( ) ;
return ;
}
// Update key layouts by retaining existing pre-computed layouts where possible
TMap < FSectionLayoutElement , FKeyDrawBatch , FDefaultSetAllocator , FLayoutElementKeyFuncs > OldKeyLayouts ;
Swap ( OldKeyLayouts , CachedKeyLayouts ) ;
FFrameRate TickResolution = Sequencer - > GetFocusedTickResolution ( ) ;
FVector2D ClipTopLeft = InPainter . SectionGeometry . AbsoluteToLocal ( InPainter . SectionClippingRect . GetTopLeft ( ) ) ;
FVector2D ClipBottomRight = InPainter . SectionGeometry . AbsoluteToLocal ( InPainter . SectionClippingRect . GetBottomRight ( ) ) ;
// Section layouts are always ordered top to bottom - skip over any that are not in the current view
for ( const FSectionLayoutElement & LayoutElement : InSectionLayout . GetElements ( ) )
{
if ( LayoutElement . GetOffset ( ) + LayoutElement . GetHeight ( ) < ClipTopLeft . Y )
{
continue ;
}
if ( LayoutElement . GetOffset ( ) > ClipBottomRight . Y )
{
break ;
}
if ( LayoutElement . GetChannels ( ) . Num ( ) = = 0 )
{
continue ;
}
FKeyDrawBatch * ExistingBatch = OldKeyLayouts . Find ( LayoutElement ) ;
if ( ! ExistingBatch )
{
// A new cache needs to be created
FKeyDrawBatch NewBatch ( LayoutElement ) ;
NewBatch . UpdateViewIndependentData ( TickResolution ) ;
NewBatch . UpdateViewDependentData ( Sequencer , InPainter , NewCachedState , ECacheFlags : : All ) ;
CachedKeyLayouts . Add ( LayoutElement , MoveTemp ( NewBatch ) ) ;
}
else
{
// This is the common path - we already have a cached key batch, we just need to check whether we need to re-generate it
ECacheFlags ThisCacheFlags = CacheFlags | ExistingBatch - > UpdateViewIndependentData ( TickResolution ) ;
// We can reuse this key layout - update all the cached key positions
ExistingBatch - > UpdateViewDependentData ( Sequencer , InPainter , NewCachedState , ThisCacheFlags ) ;
CachedKeyLayouts . Add ( LayoutElement , MoveTemp ( * ExistingBatch ) ) ;
}
}
}
void FKeyRenderer : : Paint ( const FSectionLayout & InSectionLayout , const FWidgetStyle & InWidgetStyle , const FKeyRendererPaintArgs & Args , FSequencer * Sequencer , FSequencerSectionPainter & InPainter ) const
{
FPaintStyle Style ( InWidgetStyle ) ;
UpdateKeyLayouts ( Sequencer , InPainter , InSectionLayout ) ;
for ( const FSectionLayoutElement & LayoutElement : InSectionLayout . GetElements ( ) )
{
DrawLayoutElement ( Sequencer , InPainter , LayoutElement , Style , Args ) ;
if ( const FKeyDrawBatch * KeyDrawBatch = CachedKeyLayouts . Find ( LayoutElement ) )
{
FGeometry KeyGeometry = LayoutElement . ComputeGeometry ( InPainter . SectionGeometry ) ;
if ( LayoutElement . GetType ( ) = = FSectionLayoutElement : : Single )
{
KeyDrawBatch - > DrawCurve ( Sequencer , InPainter , KeyGeometry , Style , Args ) ;
}
KeyDrawBatch - > Draw ( Sequencer , InPainter , KeyGeometry , Style , Args ) ;
}
}
}
} // namespace Sequencer
} // namespace UE