Files
UnrealEngineUWP/Engine/Source/Editor/Sequencer/Private/SequencerKeyRenderer.cpp

959 lines
34 KiB
C++
Raw Normal View History

// 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