2178 lines
77 KiB
C++
2178 lines
77 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "SSequencerSection.h"
|
|
#include "MVVM/ViewModels/SectionModel.h"
|
|
#include "MVVM/ViewModels/TrackModel.h"
|
|
#include "MVVM/ViewModels/CategoryModel.h"
|
|
#include "MVVM/ViewModels/ViewModelIterators.h"
|
|
#include "MVVM/ViewModels/SequencerEditorViewModel.h"
|
|
#include "MVVM/ViewModels/EditorSharedViewModelData.h"
|
|
#include "MVVM/Views/ITrackAreaHotspot.h"
|
|
#include "MVVM/Views/STrackAreaView.h"
|
|
#include "MVVM/Views/SCompoundTrackLaneView.h"
|
|
#include "MVVM/Views/STrackLane.h"
|
|
#include "MVVM/Views/SChannelView.h"
|
|
#include "MVVM/Extensions/IObjectBindingExtension.h"
|
|
#include "MVVM/Selection/Selection.h"
|
|
#include "AnimatedRange.h"
|
|
#include "Rendering/DrawElements.h"
|
|
#include "Styling/AppStyle.h"
|
|
#include "Styling/StyleColors.h"
|
|
#include "SequencerSelectionPreview.h"
|
|
#include "SequencerSettings.h"
|
|
#include "Editor.h"
|
|
#include "ScopedTransaction.h"
|
|
#include "Sequencer.h"
|
|
#include "SequencerSectionPainter.h"
|
|
#include "MovieSceneSequence.h"
|
|
#include "ISequencerEditTool.h"
|
|
#include "ISequencerSection.h"
|
|
#include "SequencerHotspots.h"
|
|
#include "Widgets/SOverlay.h"
|
|
#include "MovieScene.h"
|
|
#include "Fonts/FontCache.h"
|
|
#include "Fonts/FontMeasure.h"
|
|
#include "Widgets/Input/NumericTypeInterface.h"
|
|
#include "Framework/Application/SlateApplication.h"
|
|
#include "MovieSceneTimeHelpers.h"
|
|
#include "Tracks/MovieScenePropertyTrack.h"
|
|
#include "Sections/MovieSceneSubSection.h"
|
|
#include "Generators/MovieSceneEasingFunction.h"
|
|
#include "IKeyArea.h"
|
|
#include "Widgets/SWeakWidget.h"
|
|
#include "Algo/Transform.h"
|
|
#include "Tracks/IMovieSceneSectionsToKey.h"
|
|
|
|
namespace UE
|
|
{
|
|
namespace Sequencer
|
|
{
|
|
|
|
double SSequencerSection::SectionSelectionThrobEndTime = 0;
|
|
double SSequencerSection::KeySelectionThrobEndTime = 0;
|
|
|
|
|
|
/** A point on an easing curve used for rendering */
|
|
struct FEasingCurvePoint
|
|
{
|
|
FEasingCurvePoint(FVector2D InLocation, const FLinearColor& InPointColor) : Location(InLocation), Color(InPointColor) {}
|
|
|
|
/** The location of the point (x=time, y=easing value [0-1]) */
|
|
FVector2D Location;
|
|
/** The color of the point */
|
|
FLinearColor Color;
|
|
};
|
|
|
|
static bool IsSectionToKey(const UMovieSceneTrack* Track, UMovieSceneSection* SectionObject)
|
|
{
|
|
if (const IMovieSceneSectionsToKey* MultipleSectionsToKey = Cast<IMovieSceneSectionsToKey>(Track))
|
|
{
|
|
TArray<TWeakObjectPtr<UMovieSceneSection>> SectionsToKey = MultipleSectionsToKey->GetSectionsToKey();
|
|
return SectionsToKey.Contains(SectionObject);
|
|
}
|
|
return (Track->GetSectionToKey() == SectionObject);
|
|
}
|
|
|
|
struct FSequencerSectionPainterImpl : FSequencerSectionPainter
|
|
{
|
|
FSequencerSectionPainterImpl(FSequencer& InSequencer, TSharedPtr<FTrackAreaViewModel> InTrackAreaViewModel, TSharedPtr<FSectionModel> InSection, FSlateWindowElementList& _OutDrawElements, const FGeometry& InSectionGeometry, const SSequencerSection& InSectionWidget)
|
|
: FSequencerSectionPainter(_OutDrawElements, InSectionGeometry, InSection)
|
|
, Sequencer(InSequencer)
|
|
, SectionWidget(InSectionWidget)
|
|
, TimeToPixelConverter(*InSectionWidget.GetTimeToPixel())
|
|
, TrackAreaViewModel(InTrackAreaViewModel)
|
|
, bClipRectEnabled(false)
|
|
{
|
|
CalculateSelectionColor();
|
|
|
|
const ISequencerEditTool* EditTool = TrackAreaViewModel->GetEditTool();
|
|
Hotspot = EditTool ? EditTool->GetDragHotspot() : nullptr;
|
|
if (!Hotspot)
|
|
{
|
|
Hotspot = TrackAreaViewModel->GetHotspot();
|
|
}
|
|
}
|
|
|
|
FLinearColor GetFinalTintColor(const FLinearColor& Tint) const
|
|
{
|
|
FLinearColor FinalTint = STrackAreaView::BlendDefaultTrackColor(Tint);
|
|
if (bIsHighlighted && SectionModel->GetRange() != TRange<FFrameNumber>::All())
|
|
{
|
|
float Lum = FinalTint.GetLuminance() * 0.2f;
|
|
FinalTint = FinalTint + FLinearColor(Lum, Lum, Lum, 0.f);
|
|
}
|
|
|
|
FinalTint.A *= GhostAlpha;
|
|
|
|
return FinalTint;
|
|
}
|
|
|
|
virtual int32 PaintSectionBackground(const FLinearColor& Tint) override
|
|
{
|
|
using namespace UE::MovieScene;
|
|
|
|
TRange<FFrameNumber> SectionRange = SectionModel->GetRange();
|
|
|
|
const ESlateDrawEffect DrawEffects = bParentEnabled
|
|
? ESlateDrawEffect::None
|
|
: ESlateDrawEffect::DisabledEffect;
|
|
|
|
static const FSlateBrush* CollapsedSectionBackgroundBrush = FAppStyle::GetBrush("Sequencer.Section.Background_Collapsed");
|
|
static const FSlateBrush* SectionHeaderBackgroundBrush = FAppStyle::GetBrush("Sequencer.Section.Background_Header");
|
|
static const FSlateBrush* SectionContentsBackgroundBrush = FAppStyle::GetBrush("Sequencer.Section.Background_Contents");
|
|
|
|
static const FSlateBrush* CollapsedSelectedSectionOverlay = FAppStyle::GetBrush("Sequencer.Section.CollapsedSelectedSectionOverlay");
|
|
static const FSlateBrush* SectionHeaderSelectedSectionOverlay = FAppStyle::GetBrush("Sequencer.Section.SectionHeaderSelectedSectionOverlay");
|
|
|
|
FLinearColor FinalTint = GetFinalTintColor(Tint);
|
|
|
|
// Offset lower bounds and size for infinte sections so we don't draw the rounded border on the visible area
|
|
const float InfiniteLowerOffset = SectionRange.GetLowerBound().IsClosed() ? 0.f : 100.f;
|
|
const float InfiniteSizeOffset = InfiniteLowerOffset + (SectionRange.GetUpperBound().IsClosed() ? 0.f : 100.f);
|
|
|
|
FGeometry ExpandedSectionGeometry = SectionGeometry.MakeChild(
|
|
SectionGeometry.GetLocalSize() + FVector2D(InfiniteSizeOffset, 0.f),
|
|
FSlateLayoutTransform(FVector2D(-InfiniteLowerOffset, 0.f))
|
|
);
|
|
|
|
if (Sequencer.GetSequencerSettings()->ShouldShowPrePostRoll())
|
|
{
|
|
TOptional<FSlateClippingState> PreviousClipState = DrawElements.GetClippingState();
|
|
if (PreviousClipState.IsSet())
|
|
{
|
|
DrawElements.PopClip();
|
|
}
|
|
|
|
static const FSlateBrush* PreRollBrush = FAppStyle::GetBrush("Sequencer.Section.PreRoll");
|
|
float BrushHeight = 16.f, BrushWidth = 10.f;
|
|
|
|
if (SectionRange.GetLowerBound().IsClosed())
|
|
{
|
|
FFrameNumber SectionStartTime = DiscreteInclusiveLower(SectionRange);
|
|
FFrameNumber PreRollStartTime = SectionStartTime - SectionModel->GetPreRollFrames();
|
|
|
|
const float PreRollPx = TimeToPixelConverter.FrameToPixel(SectionStartTime) - TimeToPixelConverter.FrameToPixel(PreRollStartTime);
|
|
if (PreRollPx > 0)
|
|
{
|
|
const float RoundedPreRollPx = (int(PreRollPx / BrushWidth)+1) * BrushWidth;
|
|
|
|
// Round up to the nearest BrushWidth size
|
|
FGeometry PreRollArea = SectionGeometry.MakeChild(
|
|
FVector2D(RoundedPreRollPx, BrushHeight),
|
|
FSlateLayoutTransform(FVector2D(-PreRollPx, (SectionGeometry.GetLocalSize().Y - BrushHeight)*.5f))
|
|
);
|
|
|
|
FSlateDrawElement::MakeBox(
|
|
DrawElements,
|
|
LayerId,
|
|
PreRollArea.ToPaintGeometry(),
|
|
PreRollBrush,
|
|
DrawEffects
|
|
);
|
|
}
|
|
}
|
|
|
|
if (SectionRange.GetUpperBound().IsClosed())
|
|
{
|
|
FFrameNumber SectionEndTime = DiscreteExclusiveUpper(SectionRange.GetUpperBound());
|
|
FFrameNumber PostRollEndTime = SectionEndTime + SectionModel->GetPostRollFrames();
|
|
|
|
const float PostRollPx = TimeToPixelConverter.FrameToPixel(PostRollEndTime) - TimeToPixelConverter.FrameToPixel(SectionEndTime);
|
|
if (PostRollPx > 0)
|
|
{
|
|
const float RoundedPostRollPx = (int(PostRollPx / BrushWidth)+1) * BrushWidth;
|
|
const float Difference = RoundedPostRollPx - PostRollPx;
|
|
|
|
// Slate border brushes tile UVs along +ve X, so we round the arrows to a multiple of the brush width, and offset, to ensure we don't have a partial tile visible at the end
|
|
FGeometry PostRollArea = SectionGeometry.MakeChild(
|
|
FVector2D(RoundedPostRollPx, BrushHeight),
|
|
FSlateLayoutTransform(FVector2D(SectionGeometry.GetLocalSize().X - Difference, (SectionGeometry.GetLocalSize().Y - BrushHeight)*.5f))
|
|
);
|
|
|
|
FSlateDrawElement::MakeBox(
|
|
DrawElements,
|
|
LayerId,
|
|
PostRollArea.ToPaintGeometry(),
|
|
PreRollBrush,
|
|
DrawEffects
|
|
);
|
|
}
|
|
}
|
|
|
|
if (PreviousClipState.IsSet())
|
|
{
|
|
DrawElements.GetClippingManager().PushClippingState(PreviousClipState.GetValue());
|
|
}
|
|
}
|
|
|
|
// If this section has any children, we draw the section header on the top row, and a dimmed fill for all children
|
|
|
|
TViewModelPtr<IOutlinerExtension> Outliner = SectionModel->FindAncestorOfType<IOutlinerExtension>();
|
|
const bool bHasChildren = Outliner && Outliner.AsModel()->GetChildren(EViewModelListType::Outliner);
|
|
const bool bIsExpanded = Outliner && Outliner->IsExpanded();
|
|
|
|
FLinearColor BlendedTint = BlendColor(Tint).CopyWithNewOpacity(1.f);
|
|
|
|
if (!bHasChildren || !bIsExpanded)
|
|
{
|
|
FSlateDrawElement::MakeBox(
|
|
DrawElements,
|
|
LayerId,
|
|
ExpandedSectionGeometry.ToPaintGeometry(),
|
|
CollapsedSectionBackgroundBrush,
|
|
DrawEffects,
|
|
BlendedTint
|
|
);
|
|
|
|
// Draw the selection hash
|
|
if (SelectionColor.IsSet())
|
|
{
|
|
FSlateDrawElement::MakeBox(
|
|
DrawElements,
|
|
++LayerId,
|
|
ExpandedSectionGeometry.ToPaintGeometry(ExpandedSectionGeometry.GetLocalSize() - FVector2f(2.f,2.f), FSlateLayoutTransform(FVector2f(1.f, 1.f))),
|
|
CollapsedSelectedSectionOverlay,
|
|
DrawEffects,
|
|
SelectionColor.GetValue().CopyWithNewOpacity(0.8f)
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FGeometry LocalHeaderGeometry = ExpandedSectionGeometry.MakeChild(
|
|
FVector2D(ExpandedSectionGeometry.GetLocalSize().X, HeaderGeometry.GetLocalSize().Y),
|
|
FSlateLayoutTransform()
|
|
);
|
|
const float HeaderHeight = LocalHeaderGeometry.GetLocalSize().Y;
|
|
FGeometry ContentsGeometry = ExpandedSectionGeometry.MakeChild(
|
|
FVector2D(ExpandedSectionGeometry.GetLocalSize().X, ExpandedSectionGeometry.GetLocalSize().Y - HeaderHeight),
|
|
FSlateLayoutTransform(FVector2D(0.f, HeaderHeight))
|
|
);
|
|
|
|
FSlateDrawElement::MakeBox(
|
|
DrawElements,
|
|
LayerId,
|
|
LocalHeaderGeometry.ToPaintGeometry(),
|
|
SectionHeaderBackgroundBrush,
|
|
DrawEffects,
|
|
BlendedTint
|
|
);
|
|
|
|
FLinearColor FillTint = BlendedTint.LinearRGBToHSV();
|
|
FillTint.G *= .5f;
|
|
FillTint.B = FMath::Max(.03f, FillTint.B*.1f);
|
|
FSlateDrawElement::MakeBox(
|
|
DrawElements,
|
|
++LayerId,
|
|
ContentsGeometry.ToPaintGeometry(),
|
|
SectionContentsBackgroundBrush,
|
|
DrawEffects,
|
|
FillTint.HSVToLinearRGB()
|
|
);
|
|
|
|
// Draw the selection hash
|
|
if (SelectionColor.IsSet())
|
|
{
|
|
FSlateDrawElement::MakeBox(
|
|
DrawElements,
|
|
++LayerId,
|
|
LocalHeaderGeometry.ToPaintGeometry(LocalHeaderGeometry.GetLocalSize() - FVector2f(2.f, 2.f), FSlateLayoutTransform(FVector2f(1.f, 1.f))),
|
|
SectionHeaderSelectedSectionOverlay,
|
|
DrawEffects,
|
|
SelectionColor.GetValue().CopyWithNewOpacity(0.8f)
|
|
);
|
|
|
|
FLinearColor FillSelectionColor = SelectionColor.GetValue().LinearRGBToHSV();
|
|
FillSelectionColor.G *= .5f;
|
|
FillSelectionColor.B = FMath::Max(.03f, FillSelectionColor.B * .1f);
|
|
FSlateDrawElement::MakeBox(
|
|
DrawElements,
|
|
++LayerId,
|
|
ContentsGeometry.ToPaintGeometry(ContentsGeometry.GetLocalSize() - FVector2f(2.f, 2.f), FSlateLayoutTransform(FVector2f(1.f, 1.f))),
|
|
SectionHeaderSelectedSectionOverlay,
|
|
DrawEffects,
|
|
FillSelectionColor.HSVToLinearRGB().CopyWithNewOpacity(0.8f)
|
|
);
|
|
}
|
|
}
|
|
|
|
if (!ensure(!bClipRectEnabled))
|
|
{
|
|
FSlateClippingZone ClippingZone(SectionClippingRect);
|
|
DrawElements.PushClip(ClippingZone);
|
|
|
|
bClipRectEnabled = true;
|
|
}
|
|
|
|
++LayerId;
|
|
|
|
// Draw underlapping sections
|
|
DrawOverlaps(FinalTint);
|
|
|
|
// Draw empty space
|
|
DrawEmptySpace();
|
|
|
|
// Draw the blend type text
|
|
DrawBlendType();
|
|
|
|
// Draw the locked / key border
|
|
DrawBorder(FinalTint);
|
|
|
|
// Draw easing curves
|
|
DrawEasing(FinalTint);
|
|
|
|
return LayerId;
|
|
}
|
|
|
|
virtual const FTimeToPixel& GetTimeConverter() const
|
|
{
|
|
return TimeToPixelConverter;
|
|
}
|
|
|
|
void CalculateSelectionColor()
|
|
{
|
|
FSequencerSelection& Selection = *Sequencer.GetViewModel()->GetSelection();
|
|
FSequencerSelectionPreview& SelectionPreview = Sequencer.GetSelectionPreview();
|
|
|
|
ESelectionPreviewState SelectionPreviewState = SelectionPreview.GetSelectionState(SectionWidget.WeakSectionModel);
|
|
|
|
if (SelectionPreviewState == ESelectionPreviewState::NotSelected)
|
|
{
|
|
// Explicitly not selected in the preview selection
|
|
return;
|
|
}
|
|
|
|
if (SelectionPreviewState == ESelectionPreviewState::Undefined && !Selection.TrackArea.IsSelected(SectionModel))
|
|
{
|
|
// No preview selection for this section, and it's not selected
|
|
return;
|
|
}
|
|
|
|
SelectionColor = FAppStyle::GetSlateColor(SequencerSectionConstants::SelectionColorName).GetColor(FWidgetStyle());
|
|
|
|
// Use a muted selection color for selection previews
|
|
if (SelectionPreviewState == ESelectionPreviewState::Selected)
|
|
{
|
|
SelectionColor.GetValue() = SelectionColor.GetValue().LinearRGBToHSV();
|
|
SelectionColor.GetValue().R += 0.1f; // +10% hue
|
|
SelectionColor.GetValue().G = 0.6f; // 60% saturation
|
|
|
|
SelectionColor = SelectionColor.GetValue().HSVToLinearRGB();
|
|
}
|
|
|
|
SelectionColor->A *= GhostAlpha;
|
|
}
|
|
|
|
void DrawBlendType()
|
|
{
|
|
// Draw the blend type text if necessary
|
|
UMovieSceneSection* SectionObject = SectionModel->GetSection();
|
|
UMovieSceneTrack* Track = GetTrack();
|
|
if (!Track || Track->GetSupportedBlendTypes().Num() <= 1 || !SectionObject->GetBlendType().IsValid() || !bIsHighlighted || SectionObject->GetBlendType().Get() == EMovieSceneBlendType::Absolute)
|
|
{
|
|
return;
|
|
}
|
|
|
|
TSharedPtr<STrackLane> TrackLane = SectionWidget.WeakOwningTrackLane.Pin();
|
|
if (!TrackLane)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const float LaneHeight = TrackLane->GetDesiredSize().Y;
|
|
|
|
TSharedRef<FSlateFontCache> FontCache = FSlateApplication::Get().GetRenderer()->GetFontCache();
|
|
|
|
UEnum* Enum = FindObjectChecked<UEnum>(nullptr, TEXT("/Script/MovieScene.EMovieSceneBlendType"), true);
|
|
FText DisplayText = Enum->GetDisplayNameTextByValue((int64)SectionObject->GetBlendType().Get());
|
|
|
|
FSlateFontInfo FontInfo = FAppStyle::GetFontStyle("Sequencer.Section.BackgroundText");
|
|
FontInfo.Size = 24;
|
|
|
|
auto GetFontHeight = [&]
|
|
{
|
|
return FontCache->GetMaxCharacterHeight(FontInfo, 1.f) + FontCache->GetBaseline(FontInfo, 1.f);
|
|
};
|
|
while( GetFontHeight() > SectionGeometry.Size.Y && FontInfo.Size > 11 )
|
|
{
|
|
FontInfo.Size = FMath::Max(FMath::FloorToInt(FontInfo.Size - 6.f), 11);
|
|
}
|
|
|
|
const float FontHeight = GetFontHeight();
|
|
|
|
// Offset more to the right of the lower bound since there's a handle there
|
|
FVector2D TextOffset = SectionModel->GetRange().HasLowerBound() ? FVector2D(8.f, LaneHeight - FontHeight - 4.f) : FVector2D(1.f, LaneHeight - FontHeight - 4.f);
|
|
FVector2D TextPosition = SectionGeometry.AbsoluteToLocal(SectionClippingRect.GetTopLeft()) + TextOffset;
|
|
|
|
FSlateDrawElement::MakeText(
|
|
DrawElements,
|
|
LayerId,
|
|
SectionGeometry.MakeChild(
|
|
FVector2D(SectionGeometry.Size.X, FontHeight),
|
|
FSlateLayoutTransform(TextPosition)
|
|
).ToPaintGeometry(),
|
|
DisplayText,
|
|
FontInfo,
|
|
bParentEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect,
|
|
FLinearColor(1.f,1.f,1.f,.2f)
|
|
);
|
|
}
|
|
|
|
float GetEaseHighlightAmount(UMovieSceneSection* InSection, float EaseInInterp, float EaseOutInterp) const
|
|
{
|
|
using namespace UE::Sequencer;
|
|
|
|
float EaseInScale = 0.f, EaseOutScale = 0.f;
|
|
if (TSharedPtr<FSectionEasingHandleHotspot> EasingHandleHotspot = HotspotCast<FSectionEasingHandleHotspot>(Hotspot))
|
|
{
|
|
if (EasingHandleHotspot->GetSection() == InSection)
|
|
{
|
|
if (EasingHandleHotspot->HandleType == ESequencerEasingType::In)
|
|
{
|
|
EaseInScale = 1.f;
|
|
}
|
|
else
|
|
{
|
|
EaseOutScale = 1.f;
|
|
}
|
|
}
|
|
}
|
|
else if (TSharedPtr<FSectionEasingAreaHotspot> EasingAreaHotspot = HotspotCast<FSectionEasingAreaHotspot>(Hotspot))
|
|
{
|
|
for (const FEasingAreaHandle& Easing : EasingAreaHotspot->Easings)
|
|
{
|
|
TSharedPtr<FSectionModel> EasingSectionModel = Easing.WeakSectionModel.Pin();
|
|
if (EasingSectionModel && EasingSectionModel->GetSection() == InSection)
|
|
{
|
|
if (Easing.EasingType == ESequencerEasingType::In)
|
|
{
|
|
EaseInScale = 1.f;
|
|
}
|
|
else
|
|
{
|
|
EaseOutScale = 1.f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return 0.f;
|
|
}
|
|
|
|
const float TotalScale = EaseInScale + EaseOutScale;
|
|
return TotalScale > 0.f ? EaseInInterp * (EaseInScale/TotalScale) + ((1.f-EaseOutInterp) * (EaseOutScale/TotalScale)) : 0.f;
|
|
}
|
|
|
|
FEasingCurvePoint MakeCurvePoint(UMovieSceneSection* InSection, FFrameTime Time, const FLinearColor& FinalTint, const FLinearColor& EaseSelectionColor) const
|
|
{
|
|
TOptional<float> EaseInValue, EaseOutValue;
|
|
float EaseInInterp = 0.f, EaseOutInterp = 1.f;
|
|
InSection->EvaluateEasing(Time, EaseInValue, EaseOutValue, &EaseInInterp, &EaseOutInterp);
|
|
|
|
return FEasingCurvePoint(
|
|
FVector2D(Time / TimeToPixelConverter.GetTickResolution(), EaseInValue.Get(1.f) * EaseOutValue.Get(1.f)),
|
|
FMath::Lerp(FinalTint, EaseSelectionColor, GetEaseHighlightAmount(InSection, EaseInInterp, EaseOutInterp))
|
|
);
|
|
}
|
|
|
|
/** Adds intermediate control points for the specified section's easing up to a given threshold */
|
|
void RefineCurvePoints(UMovieSceneSection* SectionObject, const FLinearColor& FinalTint, const FLinearColor& EaseSelectionColor, TArray<FEasingCurvePoint>& InOutPoints)
|
|
{
|
|
static float GradientThreshold = .05f;
|
|
static float ValueThreshold = .05f;
|
|
|
|
float MinTimeSize = FMath::Max(0.0001, TimeToPixelConverter.PixelToSeconds(2.5) - TimeToPixelConverter.PixelToSeconds(0));
|
|
|
|
for (int32 Index = 0; Index < InOutPoints.Num() - 1; ++Index)
|
|
{
|
|
const FEasingCurvePoint& Lower = InOutPoints[Index];
|
|
const FEasingCurvePoint& Upper = InOutPoints[Index + 1];
|
|
|
|
if ((Upper.Location.X - Lower.Location.X)*.5f > MinTimeSize)
|
|
{
|
|
FVector2D::FReal NewPointTime = (Upper.Location.X + Lower.Location.X)*.5f;
|
|
FFrameTime FrameTime = NewPointTime * TimeToPixelConverter.GetTickResolution();
|
|
float NewPointValue = SectionObject->EvaluateEasing(FrameTime);
|
|
|
|
// Check that the gradient is changing significantly
|
|
FVector2D::FReal LinearValue = (Upper.Location.Y + Lower.Location.Y) * .5f;
|
|
FVector2D::FReal PointGradient = NewPointValue - SectionObject->EvaluateEasing(FMath::Lerp(Lower.Location.X, NewPointTime, 0.9f) * TimeToPixelConverter.GetTickResolution());
|
|
FVector2D::FReal OuterGradient = Upper.Location.Y - Lower.Location.Y;
|
|
if (!FMath::IsNearlyEqual(OuterGradient, PointGradient, GradientThreshold) ||
|
|
!FMath::IsNearlyEqual(LinearValue, NewPointValue, ValueThreshold))
|
|
{
|
|
// Add the point
|
|
InOutPoints.Insert(MakeCurvePoint(SectionObject, FrameTime, FinalTint, EaseSelectionColor), Index+1);
|
|
--Index;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void DrawEasingForSegment(const FOverlappingSections& Segment, const FGeometry& InnerSectionGeometry, const FLinearColor& FinalTint)
|
|
{
|
|
// @todo: sequencer-timecode: Test that start offset is not required here
|
|
const float RangeStartPixel = TimeToPixelConverter.FrameToPixel(UE::MovieScene::DiscreteInclusiveLower(Segment.Range));
|
|
const float RangeEndPixel = TimeToPixelConverter.FrameToPixel(UE::MovieScene::DiscreteExclusiveUpper(Segment.Range));
|
|
const float RangeSizePixel = RangeEndPixel - RangeStartPixel;
|
|
|
|
TViewModelPtr<IGeometryExtension> Geometry = SectionModel->FindAncestorOfType<IOutlinerExtension>().ImplicitCast();
|
|
const float EasingHeight = (Geometry ? Geometry->GetVirtualGeometry().GetHeight() : InnerSectionGeometry.Size.Y) - 2.f;
|
|
|
|
FGeometry RangeGeometry = InnerSectionGeometry.MakeChild(FVector2D(RangeSizePixel, EasingHeight), FSlateLayoutTransform(FVector2D(RangeStartPixel, 1.f)));
|
|
if (!FSlateRect::DoRectanglesIntersect(RangeGeometry.GetLayoutBoundingRect(), ParentClippingRect))
|
|
{
|
|
return;
|
|
}
|
|
|
|
UMovieSceneTrack* Track = GetTrack();
|
|
if (!Track)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const FSlateBrush* MyBrush = FAppStyle::Get().GetBrush("Sequencer.Timeline.EaseInOut");
|
|
|
|
FSlateResourceHandle ResourceHandle = FSlateApplication::Get().GetRenderer()->GetResourceHandle(*MyBrush);
|
|
const FSlateShaderResourceProxy* ResourceProxy = ResourceHandle.GetResourceProxy();
|
|
|
|
FVector2f AtlasOffset = ResourceProxy ? ResourceProxy->StartUV : FVector2f(0.f, 0.f);
|
|
FVector2f AtlasUVSize = ResourceProxy ? ResourceProxy->SizeUV : FVector2f(1.f, 1.f);
|
|
|
|
FSlateRenderTransform RenderTransform;
|
|
|
|
const FVector2f Pos = FVector2f(RangeGeometry.GetAbsolutePosition()); // LWC_TODO: Precision loss
|
|
const FVector2D Size = RangeGeometry.GetLocalSize();
|
|
|
|
FLinearColor EaseSelectionColor = FAppStyle::GetSlateColor(SequencerSectionConstants::SelectionColorName).GetColor(FWidgetStyle());
|
|
|
|
FColor FillColor(0,0,0,51);
|
|
|
|
TArray<FEasingCurvePoint> CurvePoints;
|
|
|
|
// Segment.Impls are already sorted bottom to top
|
|
for (int32 CurveIndex = 0; CurveIndex < Segment.Sections.Num(); ++CurveIndex)
|
|
{
|
|
UMovieSceneSection* CurveSection = Segment.Sections[CurveIndex].Pin()->GetSection();
|
|
|
|
// Make the points for the curve
|
|
CurvePoints.Reset(20);
|
|
{
|
|
CurvePoints.Add(MakeCurvePoint(CurveSection, Segment.Range.GetLowerBoundValue(), FinalTint, EaseSelectionColor));
|
|
CurvePoints.Add(MakeCurvePoint(CurveSection, Segment.Range.GetUpperBoundValue(), FinalTint, EaseSelectionColor));
|
|
|
|
// Refine the control points
|
|
int32 LastNumPoints;
|
|
do
|
|
{
|
|
LastNumPoints = CurvePoints.Num();
|
|
RefineCurvePoints(CurveSection, FinalTint, EaseSelectionColor, CurvePoints);
|
|
} while(LastNumPoints != CurvePoints.Num());
|
|
}
|
|
|
|
TArray<SlateIndex> Indices;
|
|
TArray<FSlateVertex> Verts;
|
|
TArray<FVector2D> BorderPoints;
|
|
TArray<FLinearColor> BorderPointColors;
|
|
|
|
Indices.Reserve(CurvePoints.Num()*6);
|
|
Verts.Reserve(CurvePoints.Num()*2);
|
|
|
|
const FVector2f SizeAsFloatVec = FVector2f(Size); // LWC_TODO: Precision loss
|
|
|
|
for (const FEasingCurvePoint& Point : CurvePoints)
|
|
{
|
|
float SegmentStartTime = UE::MovieScene::DiscreteInclusiveLower(Segment.Range) / TimeToPixelConverter.GetTickResolution();
|
|
float U = (Point.Location.X - SegmentStartTime) / ( FFrameNumber(UE::MovieScene::DiscreteSize(Segment.Range)) / TimeToPixelConverter.GetTickResolution() );
|
|
|
|
// Add verts top->bottom
|
|
FVector2f UV(U, 0.f);
|
|
Verts.Add(FSlateVertex::Make<ESlateVertexRounding::Disabled>(RenderTransform, (Pos + UV*SizeAsFloatVec*RangeGeometry.Scale), AtlasOffset + UV*AtlasUVSize, FillColor)); // LWC_TODO: Precision loss
|
|
|
|
UV.Y = 1.f - Point.Location.Y;
|
|
BorderPoints.Add(FVector2D(UV)*Size);
|
|
BorderPointColors.Add(Point.Color);
|
|
Verts.Add(FSlateVertex::Make<ESlateVertexRounding::Disabled>(RenderTransform, (Pos + UV*SizeAsFloatVec*RangeGeometry.Scale), AtlasOffset + FVector2f(UV.X, 0.5f)*AtlasUVSize, FillColor)); // LWC_TODO: Precision loss
|
|
|
|
if (Verts.Num() >= 4)
|
|
{
|
|
int32 Index0 = Verts.Num()-4, Index1 = Verts.Num()-3, Index2 = Verts.Num()-2, Index3 = Verts.Num()-1;
|
|
Indices.Add(Index0);
|
|
Indices.Add(Index1);
|
|
Indices.Add(Index2);
|
|
|
|
Indices.Add(Index1);
|
|
Indices.Add(Index2);
|
|
Indices.Add(Index3);
|
|
}
|
|
}
|
|
|
|
if (Indices.Num())
|
|
{
|
|
FSlateDrawElement::MakeCustomVerts(
|
|
DrawElements,
|
|
LayerId,
|
|
ResourceHandle,
|
|
Verts,
|
|
Indices,
|
|
nullptr,
|
|
0,
|
|
0, ESlateDrawEffect::PreMultipliedAlpha);
|
|
|
|
const ESlateDrawEffect DrawEffects = bParentEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect;
|
|
FSlateDrawElement::MakeLines(
|
|
DrawElements,
|
|
LayerId + 1,
|
|
RangeGeometry.ToPaintGeometry(),
|
|
BorderPoints,
|
|
BorderPointColors,
|
|
DrawEffects | ESlateDrawEffect::PreMultipliedAlpha,
|
|
FLinearColor::White,
|
|
true);
|
|
}
|
|
}
|
|
|
|
++LayerId;
|
|
}
|
|
|
|
void DrawEasing(const FLinearColor& FinalTint)
|
|
{
|
|
if (!SectionModel->GetSection()->GetBlendType().IsValid())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Compute easing geometry by insetting from the current section geometry by 1px
|
|
FGeometry InnerSectionGeometry = SectionGeometry.MakeChild(SectionGeometry.Size - FVector2D(2.f, 2.f), FSlateLayoutTransform(FVector2D(1.f, 1.f)));
|
|
for (const FOverlappingSections& Segment : SectionWidget.UnderlappingEasingSegments)
|
|
{
|
|
DrawEasingForSegment(Segment, InnerSectionGeometry, FinalTint);
|
|
}
|
|
|
|
++LayerId;
|
|
}
|
|
|
|
void DrawOverlaps(const FLinearColor& FinalTint)
|
|
{
|
|
using namespace UE::Sequencer;
|
|
|
|
FGeometry InnerSectionGeometry = SectionGeometry.MakeChild(SectionGeometry.Size - FVector2D(2.f, 2.f), FSlateLayoutTransform(FVector2D(1.f, 1.f)));
|
|
|
|
UMovieSceneTrack* Track = GetTrack();
|
|
if (!Track)
|
|
{
|
|
return;
|
|
}
|
|
|
|
static const FSlateBrush* PinCusionBrush = FAppStyle::GetBrush("Sequencer.Section.PinCusion");
|
|
static const FSlateBrush* OverlapBorderBrush = FAppStyle::GetBrush("Sequencer.Section.OverlapBorder");
|
|
|
|
const ESlateDrawEffect DrawEffects = bParentEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect;
|
|
|
|
const float StartTimePixel = SectionModel->GetSection()->HasStartFrame() ? TimeToPixelConverter.FrameToPixel(SectionModel->GetSection()->GetInclusiveStartFrame()) : 0.f;
|
|
|
|
for (int32 SegmentIndex = 0; SegmentIndex < SectionWidget.UnderlappingSegments.Num(); ++SegmentIndex)
|
|
{
|
|
const FOverlappingSections& Segment = SectionWidget.UnderlappingSegments[SegmentIndex];
|
|
|
|
const float RangeStartPixel = Segment.Range.GetLowerBound().IsOpen() ? 0.f : TimeToPixelConverter.FrameToPixel(UE::MovieScene::DiscreteInclusiveLower(Segment.Range));
|
|
const float RangeEndPixel = Segment.Range.GetUpperBound().IsOpen() ? InnerSectionGeometry.Size.X : TimeToPixelConverter.FrameToPixel(UE::MovieScene::DiscreteExclusiveUpper(Segment.Range));
|
|
const float RangeSizePixel = RangeEndPixel - RangeStartPixel;
|
|
|
|
FGeometry RangeGeometry = InnerSectionGeometry.MakeChild(FVector2D(RangeSizePixel, InnerSectionGeometry.Size.Y), FSlateLayoutTransform(FVector2D(RangeStartPixel - StartTimePixel, 0.f)));
|
|
if (!FSlateRect::DoRectanglesIntersect(RangeGeometry.GetLayoutBoundingRect(), ParentClippingRect))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const FOverlappingSections* NextSegment = SegmentIndex < SectionWidget.UnderlappingSegments.Num() - 1 ? &SectionWidget.UnderlappingSegments[SegmentIndex+1] : nullptr;
|
|
const bool bDrawRightMostBound = !NextSegment || !Segment.Range.Adjoins(NextSegment->Range);
|
|
|
|
FSlateDrawElement::MakeBox(
|
|
DrawElements,
|
|
LayerId,
|
|
RangeGeometry.ToPaintGeometry(),
|
|
PinCusionBrush,
|
|
DrawEffects,
|
|
FinalTint
|
|
);
|
|
|
|
FPaintGeometry PaintGeometry = bDrawRightMostBound ? RangeGeometry.ToPaintGeometry() : RangeGeometry.ToPaintGeometry(FVector2D(RangeGeometry.Size) + FVector2D(10.f, 0.f), FSlateLayoutTransform(FVector2D::ZeroVector));
|
|
FSlateDrawElement::MakeBox(
|
|
DrawElements,
|
|
LayerId,
|
|
PaintGeometry,
|
|
OverlapBorderBrush,
|
|
DrawEffects,
|
|
FLinearColor(1.f,1.f,1.f,.3f)
|
|
);
|
|
}
|
|
|
|
++LayerId;
|
|
}
|
|
|
|
void DrawEmptySpace()
|
|
{
|
|
using namespace UE::Sequencer;
|
|
|
|
TSharedPtr<STrackLane> OwningTrackLane = SectionWidget.WeakOwningTrackLane.Pin();
|
|
if (!OwningTrackLane)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Find all our children
|
|
TSet<FWeakViewModelPtr> AllDescendents;
|
|
for (const FViewModelPtr& Descendent : SectionModel->GetDescendants())
|
|
{
|
|
AllDescendents.Add(Descendent);
|
|
}
|
|
|
|
// Find all our child track lanes
|
|
TArray<TSharedPtr<STrackLane>> EmptyChildLanes;
|
|
Algo::Transform(OwningTrackLane->GetChildLanes(), EmptyChildLanes, [](const TWeakPtr<STrackLane>& In){ return In.Pin(); });
|
|
|
|
// Remove any invalid track lanes, or track lanes that we made a widget for
|
|
for (int32 Index = EmptyChildLanes.Num()-1; Index >= 0; --Index)
|
|
{
|
|
if (EmptyChildLanes[Index])
|
|
{
|
|
for (const TPair<FWeakViewModelPtr, TSharedPtr<ITrackLaneWidget>>& Pair : EmptyChildLanes[Index]->GetAllWidgets())
|
|
{
|
|
if (AllDescendents.Contains(Pair.Key))
|
|
{
|
|
EmptyChildLanes.RemoveAtSwap(Index, EAllowShrinking::No);
|
|
// Move onto the next child lane
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
EmptyChildLanes.RemoveAtSwap(Index, EAllowShrinking::No);
|
|
}
|
|
}
|
|
|
|
// Paint empty space for remaining track lanes
|
|
const ESlateDrawEffect DrawEffects = bParentEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect;
|
|
static const FSlateBrush* EmptySpaceBrush = FAppStyle::GetBrush("Sequencer.Section.EmptySpace");
|
|
|
|
for (TSharedPtr<STrackLane> EmptyTrackLane : EmptyChildLanes)
|
|
{
|
|
const float LaneTop = EmptyTrackLane->GetVerticalPosition() - OwningTrackLane->GetVerticalPosition();
|
|
const FVector2D LaneSize(SectionGeometry.GetLocalSize().X, EmptyTrackLane->GetOutlinerItem()->GetOutlinerSizing().Height);
|
|
|
|
FSlateDrawElement::MakeBox(
|
|
DrawElements,
|
|
LayerId,
|
|
SectionGeometry.MakeChild(
|
|
LaneSize,
|
|
FSlateLayoutTransform(FVector2D(0.f, LaneTop))
|
|
).ToPaintGeometry(),
|
|
EmptySpaceBrush,
|
|
DrawEffects
|
|
);
|
|
}
|
|
|
|
++LayerId;
|
|
}
|
|
|
|
void DrawBorder(const FLinearColor& FinalTint)
|
|
{
|
|
UMovieSceneSection* SectionObject = SectionModel->GetSection();
|
|
UMovieSceneTrack* Track = SectionObject->GetTypedOuter<UMovieSceneTrack>();
|
|
|
|
// Show the locked border if it's locked or read only
|
|
const bool bLocked = SectionObject->IsLocked() || SectionObject->IsReadOnly();
|
|
|
|
// Only show section to key border if we have more than one section
|
|
const bool bIsSectionToKey = Track && Track->GetAllSections().Num() > 1 && IsSectionToKey(Track,SectionObject);
|
|
|
|
const ESlateDrawEffect DrawEffects = bParentEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect;
|
|
|
|
if (bLocked)
|
|
{
|
|
static const FName SelectionBorder("Sequencer.Section.LockedBorder");
|
|
|
|
FSlateDrawElement::MakeBox(
|
|
DrawElements,
|
|
LayerId,
|
|
SectionGeometry.ToPaintGeometry(),
|
|
FAppStyle::GetBrush(SelectionBorder),
|
|
DrawEffects,
|
|
FStyleColors::Error.GetColor(FWidgetStyle())
|
|
);
|
|
++LayerId;
|
|
}
|
|
else if (bIsSectionToKey)
|
|
{
|
|
static const FName SelectionBorder("Sequencer.Section.LockedBorder");
|
|
|
|
FSlateDrawElement::MakeBox(
|
|
DrawElements,
|
|
LayerId,
|
|
SectionGeometry.ToPaintGeometry(),
|
|
FAppStyle::GetBrush(SelectionBorder),
|
|
DrawEffects,
|
|
FStyleColors::Success.GetColor(FWidgetStyle())
|
|
);
|
|
++LayerId;
|
|
}
|
|
}
|
|
|
|
TOptional<FLinearColor> SelectionColor;
|
|
FSequencer& Sequencer;
|
|
const SSequencerSection& SectionWidget;
|
|
FTimeToPixel TimeToPixelConverter;
|
|
TSharedPtr<FTrackAreaViewModel> TrackAreaViewModel;
|
|
TSharedPtr<ITrackAreaHotspot> Hotspot;
|
|
|
|
/** The clipping rectangle of the parent widget */
|
|
FSlateRect ParentClippingRect;
|
|
|
|
bool bClipRectEnabled;
|
|
};
|
|
|
|
void SSequencerSection::Construct( const FArguments& InArgs, TSharedPtr<FSequencer> InSequencer, TSharedPtr<FSectionModel> InSectionModel, TSharedPtr<STrackLane> OwningTrackLane)
|
|
{
|
|
using namespace UE::MovieScene;
|
|
|
|
Sequencer = InSequencer;
|
|
WeakSectionModel = InSectionModel;
|
|
WeakOwningTrackLane = OwningTrackLane;
|
|
SectionInterface = InSectionModel->GetSectionInterface();
|
|
HandleOffsetPx = 0.f;
|
|
TimeToPixel = MakeShared<FTimeToPixel>(100.f, TRange<double>(0.0, 1.0), FFrameRate());
|
|
|
|
SetEnabled(MakeAttributeSP(this, &SSequencerSection::IsEnabled));
|
|
SetToolTipText(MakeAttributeSP(this, &SSequencerSection::GetToolTipText));
|
|
|
|
UMovieSceneSection* Section = InSectionModel->GetSection();
|
|
UMovieSceneTrack* Track = Section ? Section->GetTypedOuter<UMovieSceneTrack>() : nullptr;
|
|
if (ensure(Track))
|
|
{
|
|
Track->EventHandlers.Link(TrackModifiedBinding, this);
|
|
}
|
|
|
|
UpdateUnderlappingSegments();
|
|
|
|
TSharedRef<SOverlay> Overlay =
|
|
|
|
SNew(SOverlay)
|
|
|
|
+ SOverlay::Slot(FCreateSectionViewWidgetParams::CompoundTrackLaneViewOrder)
|
|
[
|
|
SAssignNew(ChildLaneWidgets, SCompoundTrackLaneView, SharedThis(this))
|
|
]
|
|
|
|
+ SOverlay::Slot(FCreateSectionViewWidgetParams::ChannelViewOrder)
|
|
[
|
|
SNew(SChannelView, InSectionModel, OwningTrackLane->GetTrackAreaView())
|
|
.KeyBarColor(this, &SSequencerSection::GetTopLevelKeyBarColor)
|
|
.Visibility(this, &SSequencerSection::GetTopLevelChannelGroupVisibility)
|
|
];
|
|
|
|
FCreateSectionViewWidgetParams Params{
|
|
Overlay,
|
|
SharedThis(this),
|
|
OwningTrackLane.ToSharedRef(),
|
|
OwningTrackLane->GetTrackAreaView().ToSharedRef(),
|
|
InSectionModel.ToSharedRef()
|
|
};
|
|
SectionInterface->CreateViewWidgets(Params);
|
|
|
|
ChildSlot
|
|
.Padding(MakeAttributeSP(this, &SSequencerSection::GetHandleOffsetPadding))
|
|
[
|
|
Overlay
|
|
];
|
|
}
|
|
|
|
SSequencerSection::~SSequencerSection()
|
|
{
|
|
TSharedPtr<FTrackAreaViewModel> TrackAreaViewModel = GetTrackAreaViewModel();
|
|
if (TrackAreaViewModel.IsValid())
|
|
{
|
|
TrackAreaViewModel->SetHotspot(nullptr);
|
|
}
|
|
}
|
|
|
|
EVisibility SSequencerSection::GetTopLevelChannelGroupVisibility() const
|
|
{
|
|
using namespace UE::MovieScene;
|
|
|
|
TSharedPtr<FSectionModel> SectionModel = WeakSectionModel.Pin();
|
|
TViewModelPtr<IOutlinerExtension> Outliner = SectionModel ? SectionModel->FindAncestorOfType<IOutlinerExtension>() : nullptr;
|
|
|
|
if (!SectionModel || !Outliner || Outliner->IsExpanded() || !SectionModel->GetDescendantsOfType<FChannelModel>())
|
|
{
|
|
return EVisibility::Collapsed;
|
|
}
|
|
|
|
return EVisibility::Visible;
|
|
}
|
|
|
|
FLinearColor SSequencerSection::GetTopLevelKeyBarColor() const
|
|
{
|
|
TSharedPtr<FSectionModel> SectionModel = WeakSectionModel.Pin();
|
|
TSharedPtr<ITrackExtension> Track = SectionModel ? SectionModel->FindAncestorOfType<ITrackExtension>() : nullptr;
|
|
UMovieSceneTrack* TrackObject = Track ? Track->GetTrack() : nullptr;
|
|
|
|
FLinearColor Tint = FLinearColor::White;
|
|
if (TrackObject)
|
|
{
|
|
Tint = FSequencerSectionPainter::BlendColor(TrackObject->GetColorTint()).CopyWithNewOpacity(1.f).LinearRGBToHSV();
|
|
// These values relate to those that are draw in the expanded section fill in PaintSectionBackground
|
|
// Except the Saturation is 75% rather than 50%, and the value is 25% rather than 10%
|
|
//Tint.G *= .75f;
|
|
Tint.B = FMath::Max(.03f, Tint.B*.25f);
|
|
Tint = Tint.HSVToLinearRGB();
|
|
}
|
|
|
|
return Tint;
|
|
}
|
|
|
|
FMargin SSequencerSection::GetHandleOffsetPadding() const
|
|
{
|
|
return FMargin(HandleOffsetPx, 0.0);
|
|
}
|
|
|
|
FText SSequencerSection::GetToolTipText() const
|
|
{
|
|
const UMovieSceneSection* SectionObject = SectionInterface->GetSectionObject();
|
|
const UMovieScene* MovieScene = SectionObject ? SectionObject->GetTypedOuter<UMovieScene>() : nullptr;
|
|
|
|
// Optional section specific content to add to tooltip
|
|
FText SectionToolTipContent = SectionInterface->GetSectionToolTip();
|
|
|
|
FText SectionTitleText = SectionInterface->GetSectionTitle();
|
|
if (!SectionTitleText.IsEmpty())
|
|
{
|
|
SectionTitleText = FText::Format(FText::FromString(TEXT("{0}\n")), SectionTitleText);
|
|
}
|
|
|
|
// If the objects are valid and the section is not unbounded, add frame information to the tooltip
|
|
if (SectionObject && MovieScene && SectionObject->HasStartFrame() && SectionObject->HasEndFrame())
|
|
{
|
|
FFrameRate TickResolution = MovieScene->GetTickResolution();
|
|
FFrameRate DisplayRate = MovieScene->GetDisplayRate();
|
|
|
|
int32 StartFrame = ConvertFrameTime(SectionObject->GetInclusiveStartFrame(), MovieScene->GetTickResolution(), MovieScene->GetDisplayRate()).RoundToFrame().Value;
|
|
int32 EndFrame = ConvertFrameTime(SectionObject->GetExclusiveEndFrame(), MovieScene->GetTickResolution(), MovieScene->GetDisplayRate()).RoundToFrame().Value;
|
|
|
|
FText SectionToolTip;
|
|
if (SectionToolTipContent.IsEmpty())
|
|
{
|
|
SectionToolTip = FText::Format(NSLOCTEXT("SequencerSection", "TooltipFormat", "{0}{1} - {2} ({3} frames)"), SectionTitleText,
|
|
StartFrame,
|
|
EndFrame,
|
|
EndFrame - StartFrame);
|
|
}
|
|
else
|
|
{
|
|
SectionToolTip = FText::Format(NSLOCTEXT("SequencerSection", "TooltipFormatWithSectionContent", "{0}{1} - {2} ({3} frames)\n{4}"), SectionTitleText,
|
|
StartFrame,
|
|
EndFrame,
|
|
EndFrame - StartFrame,
|
|
SectionToolTipContent);
|
|
}
|
|
|
|
if (SectionObject->Easing.EaseIn.GetObject() && SectionObject->Easing.GetEaseInDuration() > 0)
|
|
{
|
|
int32 EaseInFrames = ConvertFrameTime(SectionObject->Easing.GetEaseInDuration(), TickResolution, DisplayRate).RoundToFrame().Value;
|
|
FText EaseInText = FText::Format(NSLOCTEXT("SequencerSection", "EaseInFormat", "Ease In: {0} ({1} frames)"), SectionObject->Easing.EaseIn->GetDisplayName(), EaseInFrames);
|
|
SectionToolTip = FText::Join(FText::FromString("\n"), SectionToolTip, EaseInText);
|
|
}
|
|
|
|
if (SectionObject->Easing.EaseOut.GetObject() && SectionObject->Easing.GetEaseOutDuration() > 0)
|
|
{
|
|
int32 EaseOutFrames = ConvertFrameTime(SectionObject->Easing.GetEaseOutDuration(), TickResolution, DisplayRate).RoundToFrame().Value;
|
|
FText EaseOutText = FText::Format(NSLOCTEXT("SequencerSection", "EaseOutFormat", "Ease Out: {0} ({1} frames)"), SectionObject->Easing.EaseOut->GetDisplayName(), EaseOutFrames);
|
|
SectionToolTip = FText::Join(FText::FromString("\n"), SectionToolTip, EaseOutText);
|
|
}
|
|
|
|
return SectionToolTip;
|
|
}
|
|
else
|
|
{
|
|
if (SectionToolTipContent.IsEmpty())
|
|
{
|
|
return SectionInterface->GetSectionTitle();
|
|
}
|
|
else
|
|
{
|
|
return FText::Format(NSLOCTEXT("SequencerSection", "TooltipSectionContentFormat", "{0}{1}"), SectionTitleText, SectionToolTipContent);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SSequencerSection::IsEnabled() const
|
|
{
|
|
return !SectionInterface->IsReadOnly();
|
|
}
|
|
|
|
FVector2D SSequencerSection::ComputeDesiredSize(float) const
|
|
{
|
|
return FVector2D(0.f, 0.f);
|
|
}
|
|
|
|
void SSequencerSection::ReportParentGeometry(const FGeometry& InParentGeometry)
|
|
{
|
|
ParentGeometry = InParentGeometry;
|
|
}
|
|
|
|
FTrackLaneScreenAlignment SSequencerSection::GetAlignment(const ITrackLaneWidgetSpace& InScreenSpace, const FGeometry& InParentGeometry) const
|
|
{
|
|
using namespace UE::MovieScene;
|
|
TSharedPtr<FSectionModel> SectionModel = WeakSectionModel.Pin();
|
|
if (!SectionModel)
|
|
{
|
|
return FTrackLaneScreenAlignment();
|
|
}
|
|
|
|
FTrackLaneVirtualAlignment VirtualAlignment = SectionModel->ArrangeVirtualTrackLaneView();
|
|
FTrackLaneScreenAlignment ScreenAlignment = VirtualAlignment.ToScreen(InScreenSpace.GetScreenSpace(VirtualAlignment.ViewSpaceID), InParentGeometry);
|
|
|
|
if (TOptional<FFrameNumber> FiniteLength = VirtualAlignment.GetFiniteLength())
|
|
{
|
|
constexpr float MinSectionWidth = 1.f;
|
|
|
|
const float FinalSectionWidth = FMath::Max(MinSectionWidth + SectionInterface->GetSectionGripSize() * 2.f, ScreenAlignment.WidthPx);
|
|
const float GripOffset = (FinalSectionWidth - ScreenAlignment.WidthPx) / 2.f;
|
|
|
|
ScreenAlignment.LeftPosPx -= GripOffset;
|
|
ScreenAlignment.WidthPx = FMath::Max(FinalSectionWidth, MinSectionWidth + SectionInterface->GetSectionGripSize() * 2.f);
|
|
}
|
|
|
|
return ScreenAlignment;
|
|
}
|
|
|
|
int32 SSequencerSection::GetOverlapPriority() const
|
|
{
|
|
if (TSharedPtr<FSectionModel> SectionModel = WeakSectionModel.Pin())
|
|
{
|
|
if (UMovieSceneSection* Section = SectionModel->GetSection())
|
|
{
|
|
return Section->GetOverlapPriority();
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool SSequencerSection::CheckForEasingHandleInteraction( const FPointerEvent& MouseEvent, const FGeometry& AllottedGeometry )
|
|
{
|
|
UMovieSceneSection* ThisSection = SectionInterface->GetSectionObject();
|
|
if (!ThisSection)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
UMovieSceneTrack* Track = ThisSection->GetTypedOuter<UMovieSceneTrack>();
|
|
if (!Track || Track->GetSupportedBlendTypes().Num() == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FMovieSceneSupportsEasingParams SupportsEasingParams(ThisSection);
|
|
EMovieSceneTrackEasingSupportFlags EasingFlags = Track->SupportsEasing(SupportsEasingParams);
|
|
if (!EnumHasAnyFlags(EasingFlags, EMovieSceneTrackEasingSupportFlags::ManualEasing))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FGeometry SectionGeometry = MakeHandlesGeometry(AllottedGeometry);
|
|
|
|
const double MousePositionX = SectionGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition()).X;
|
|
|
|
// Easing handle is a square in the top corner of the grip handle.
|
|
const double GripSizePx = SectionInterface->GetSectionGripSize();
|
|
const double GripHalfSizePx = GripSizePx / 2.f;
|
|
|
|
// Now test individual easing handles if we're at the correct vertical position
|
|
float LocalMouseY = SectionGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition()).Y;
|
|
if (LocalMouseY < 0.f || LocalMouseY > GripSizePx)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Gather all underlapping sections
|
|
TArray<TSharedPtr<FSectionModel>> AllUnderlappingSections;
|
|
if (TSharedPtr<FSectionModel> SectionModel = WeakSectionModel.Pin())
|
|
{
|
|
AllUnderlappingSections.Add(SectionModel);
|
|
}
|
|
for (const FOverlappingSections& Segment : UnderlappingSegments)
|
|
{
|
|
for (const TWeakPtr<FSectionModel>& Section : Segment.Sections)
|
|
{
|
|
AllUnderlappingSections.AddUnique(Section.Pin());
|
|
}
|
|
}
|
|
|
|
TSharedPtr<FTrackAreaViewModel> TrackAreaViewModel = GetTrackAreaViewModel();
|
|
for (const TSharedPtr<FSectionModel>& SectionModel : AllUnderlappingSections)
|
|
{
|
|
UMovieSceneSection* EasingSectionObj = SectionModel->GetSection();
|
|
|
|
if (EasingSectionObj->HasStartFrame() && EnumHasAllFlags(EasingFlags, EMovieSceneTrackEasingSupportFlags::ManualEaseIn))
|
|
{
|
|
// Compute the ease-in handle screen position.
|
|
const TRange<FFrameNumber> EaseInRange = EasingSectionObj->GetEaseInRange();
|
|
const FFrameNumber HandleTime = EaseInRange.IsEmpty() ? EasingSectionObj->GetInclusiveStartFrame() : EaseInRange.GetUpperBoundValue();
|
|
const double HandlePosition = TimeToPixel->FrameToPixel(HandleTime) - HandleOffsetPx;
|
|
|
|
if (FMath::IsNearlyEqual(MousePositionX, HandlePosition + GripHalfSizePx, GripHalfSizePx))
|
|
{
|
|
TrackAreaViewModel->SetHotspot(MakeShared<FSectionEasingHandleHotspot>(ESequencerEasingType::In, SectionModel, Sequencer));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (EasingSectionObj->HasEndFrame() && EnumHasAllFlags(EasingFlags, EMovieSceneTrackEasingSupportFlags::ManualEaseOut))
|
|
{
|
|
// Compute the ease-out handle screen position.
|
|
TRange<FFrameNumber> EaseOutRange = EasingSectionObj->GetEaseOutRange();
|
|
const FFrameNumber HandleTime = EaseOutRange.IsEmpty() ? EasingSectionObj->GetExclusiveEndFrame() : EaseOutRange.GetLowerBoundValue();
|
|
const double HandlePosition = TimeToPixel->FrameToPixel(HandleTime) + HandleOffsetPx;
|
|
|
|
if (FMath::IsNearlyEqual(MousePositionX, HandlePosition - GripHalfSizePx, GripHalfSizePx))
|
|
{
|
|
TrackAreaViewModel->SetHotspot(MakeShared<FSectionEasingHandleHotspot>(ESequencerEasingType::Out, SectionModel, Sequencer));
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool SSequencerSection::CheckForEdgeInteraction( const FPointerEvent& MouseEvent, const FGeometry& AllottedGeometry )
|
|
{
|
|
UMovieSceneSection* ThisSection = SectionInterface->GetSectionObject();
|
|
if (!ThisSection)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
TSharedPtr<FTrackAreaViewModel> TrackAreaViewModel = GetTrackAreaViewModel();
|
|
const ISequencerEditTool* EditTool = TrackAreaViewModel->GetEditTool();
|
|
TSharedPtr<ITrackAreaHotspot> Hotspot = EditTool ? EditTool->GetDragHotspot() : nullptr;
|
|
if (!Hotspot)
|
|
{
|
|
Hotspot = TrackAreaViewModel->GetHotspot();
|
|
}
|
|
|
|
if (Hotspot && !Hotspot->IsA<FSectionHotspotBase>())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
TArray<TSharedPtr<FSectionModel>> AllUnderlappingSections;
|
|
if (TSharedPtr<FSectionModel> SectionModel = WeakSectionModel.Pin())
|
|
{
|
|
AllUnderlappingSections.Add(SectionModel);
|
|
}
|
|
for (const FOverlappingSections& Segment : UnderlappingSegments)
|
|
{
|
|
for (const TWeakPtr<FSectionModel>& Section : Segment.Sections)
|
|
{
|
|
AllUnderlappingSections.AddUnique(Section.Pin());
|
|
}
|
|
}
|
|
|
|
FGeometry SectionGeometry = MakeHandlesGeometry(AllottedGeometry);
|
|
|
|
for (const TSharedPtr<FSectionModel>& UnderlappingSection : AllUnderlappingSections)
|
|
{
|
|
UMovieSceneSection* UnderlappingSectionObj = UnderlappingSection->GetSection();
|
|
TSharedPtr<ISequencerSection> UnderlappingSectionInterface = UnderlappingSection->GetSectionInterface();
|
|
if (!UnderlappingSectionInterface->SectionIsResizable())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const float ThisHandleOffset = UnderlappingSectionObj == ThisSection ? HandleOffsetPx : 0.f;
|
|
const FVector2D GripSize( UnderlappingSectionInterface->GetSectionGripSize(), SectionGeometry.Size.Y );
|
|
|
|
if (UnderlappingSectionObj->HasStartFrame())
|
|
{
|
|
// Make areas to the left and right of the geometry. We will use these areas to determine if someone dragged the left or right edge of a section
|
|
FGeometry SectionRectLeft = SectionGeometry.MakeChild(
|
|
GripSize,
|
|
FSlateLayoutTransform(FVector2D( TimeToPixel->FrameToPixel(UnderlappingSectionObj->GetInclusiveStartFrame()) - ThisHandleOffset, 0.f ))
|
|
);
|
|
|
|
if( SectionRectLeft.IsUnderLocation( MouseEvent.GetScreenSpacePosition() ) )
|
|
{
|
|
TrackAreaViewModel->SetHotspot(MakeShareable( new FSectionResizeHotspot(FSectionResizeHotspot::Left, UnderlappingSection, Sequencer)) );
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (UnderlappingSectionObj->HasEndFrame())
|
|
{
|
|
FGeometry SectionRectRight = SectionGeometry.MakeChild(
|
|
GripSize,
|
|
FSlateLayoutTransform(FVector2D( TimeToPixel->FrameToPixel(UnderlappingSectionObj->GetExclusiveEndFrame()) - UnderlappingSectionInterface->GetSectionGripSize() + ThisHandleOffset, 0 ))
|
|
);
|
|
|
|
if( SectionRectRight.IsUnderLocation( MouseEvent.GetScreenSpacePosition() ) )
|
|
{
|
|
TrackAreaViewModel->SetHotspot(MakeShareable( new FSectionResizeHotspot(FSectionResizeHotspot::Right, UnderlappingSection, Sequencer)) );
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool SSequencerSection::CheckForEasingAreaInteraction( const FPointerEvent& MouseEvent, const FGeometry& AllottedGeometry )
|
|
{
|
|
TSharedPtr<FSectionModel> ThisSectionModel = WeakSectionModel.Pin();
|
|
if (!ThisSectionModel)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FVector2D LocalMousePos = AllottedGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
|
|
|
|
TViewModelPtr<IGeometryExtension> HeaderGeometry = ThisSectionModel->FindAncestorOfType<IOutlinerExtension>().ImplicitCast();
|
|
const float EasingHeight = HeaderGeometry ? HeaderGeometry->GetVirtualGeometry().GetHeight() : AllottedGeometry.GetLocalSize().Y;
|
|
|
|
if (LocalMousePos.Y < 0 || LocalMousePos.Y > EasingHeight)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
UMovieSceneSection* ThisSection = SectionInterface->GetSectionObject();
|
|
const FFrameNumber MouseTime = TimeToPixel->PixelToFrame(LocalMousePos.X).FrameNumber;
|
|
|
|
// First off, set the hotspot to an easing area if necessary
|
|
TSharedPtr<FTrackAreaViewModel> TrackAreaViewModel = GetTrackAreaViewModel();
|
|
for (const FOverlappingSections& Segment : UnderlappingEasingSegments)
|
|
{
|
|
if (!Segment.Range.Contains(MouseTime))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
TArray<FEasingAreaHandle> EasingAreas;
|
|
for (const TWeakPtr<FSectionModel>& SectionModel : Segment.Sections)
|
|
{
|
|
UMovieSceneSection* Section = SectionModel.Pin()->GetSection();
|
|
if (Section->GetEaseInRange().Contains(MouseTime))
|
|
{
|
|
EasingAreas.Add(FEasingAreaHandle{ SectionModel, ESequencerEasingType::In });
|
|
}
|
|
if (Section->GetEaseOutRange().Contains(MouseTime))
|
|
{
|
|
EasingAreas.Add(FEasingAreaHandle{ SectionModel, ESequencerEasingType::Out });
|
|
}
|
|
}
|
|
|
|
if (EasingAreas.Num())
|
|
{
|
|
TrackAreaViewModel->SetHotspot(MakeShared<FSectionEasingAreaHotspot>(EasingAreas, WeakSectionModel, Sequencer));
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool SSequencerSection::CheckForSectionInteraction( const FPointerEvent& MouseEvent, const FGeometry& AllottedGeometry )
|
|
{
|
|
FGeometry SectionGeometry = MakeSectionGeometryWithoutHandles(AllottedGeometry);
|
|
if (SectionGeometry.IsUnderLocation(MouseEvent.GetScreenSpacePosition()))
|
|
{
|
|
TSharedPtr<FTrackAreaViewModel> TrackAreaViewModel = GetTrackAreaViewModel();
|
|
TrackAreaViewModel->SetHotspot( MakeShareable( new FSectionHotspot(WeakSectionModel, Sequencer)) );
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
FSequencer& SSequencerSection::GetSequencer() const
|
|
{
|
|
return *Sequencer.Pin().Get();
|
|
}
|
|
|
|
TSharedPtr<STrackAreaView> SSequencerSection::GetTrackAreaView() const
|
|
{
|
|
if (TSharedPtr<STrackLane> OwningTrackLane = WeakOwningTrackLane.Pin())
|
|
{
|
|
return OwningTrackLane->GetTrackAreaView();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
TSharedPtr<FTrackAreaViewModel> SSequencerSection::GetTrackAreaViewModel() const
|
|
{
|
|
if (TSharedPtr<STrackAreaView> TrackAreaView = GetTrackAreaView())
|
|
{
|
|
return TrackAreaView->GetViewModel();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void DrawFrameTimeHint(FSequencerSectionPainter& InPainter, const FFrameTime& CurrentTime, const FFrameTime& FrameTime, const TSharedPtr<INumericTypeInterface<double>> FrameNumberInterface, int32 LayerId)
|
|
{
|
|
FString FrameTimeString;
|
|
if (FrameNumberInterface)
|
|
{
|
|
FrameTimeString = FrameNumberInterface->ToString(FrameTime.AsDecimal());
|
|
}
|
|
else
|
|
{
|
|
FrameTimeString = FString::FromInt(FrameTime.GetFrame().Value);
|
|
}
|
|
|
|
const FSlateFontInfo SmallLayoutFont = FCoreStyle::GetDefaultFontStyle("Bold", 10);
|
|
const TSharedRef< FSlateFontMeasure > FontMeasureService = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
|
|
FVector2D TextSize = FontMeasureService->Measure(FrameTimeString, SmallLayoutFont);
|
|
|
|
const float PixelX = InPainter.GetTimeConverter().FrameToPixel(CurrentTime);
|
|
|
|
// Flip the text position if getting near the end of the view range
|
|
static const float TextOffsetPx = 10.f;
|
|
bool bDrawLeft = (InPainter.SectionGeometry.Size.X - PixelX) < (TextSize.X + 22.f) - TextOffsetPx;
|
|
float TextPosition = bDrawLeft ? PixelX - TextSize.X - TextOffsetPx : PixelX + TextOffsetPx;
|
|
//handle mirrored labels
|
|
const float MajorTickHeight = 9.0f;
|
|
FVector2D TextOffset(TextPosition, InPainter.SectionGeometry.Size.Y - (MajorTickHeight + TextSize.Y));
|
|
|
|
const FLinearColor DrawColor = FAppStyle::GetSlateColor("SelectionColor").GetColor(FWidgetStyle()).CopyWithNewOpacity(InPainter.GhostAlpha);
|
|
const FVector2D BoxPadding = FVector2D(4.0f, 2.0f);
|
|
// draw time string
|
|
|
|
FSlateDrawElement::MakeBox(
|
|
InPainter.DrawElements,
|
|
LayerId,
|
|
InPainter.SectionGeometry.ToPaintGeometry(TextSize + 2.0f * BoxPadding, FSlateLayoutTransform(TextOffset - BoxPadding)),
|
|
FAppStyle::GetBrush("WhiteBrush"),
|
|
ESlateDrawEffect::None,
|
|
FLinearColor::Black.CopyWithNewOpacity(0.5f * InPainter.GhostAlpha)
|
|
);
|
|
|
|
ESlateDrawEffect DrawEffects = InPainter.bParentEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect;
|
|
|
|
FSlateDrawElement::MakeText(
|
|
InPainter.DrawElements,
|
|
LayerId,
|
|
InPainter.SectionGeometry.ToPaintGeometry(TextSize, FSlateLayoutTransform(TextOffset)),
|
|
FrameTimeString,
|
|
SmallLayoutFont,
|
|
DrawEffects,
|
|
DrawColor
|
|
);
|
|
}
|
|
|
|
int32 SSequencerSection::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const
|
|
{
|
|
TSharedPtr<FSectionModel> SectionModel = WeakSectionModel.Pin();
|
|
UMovieSceneSection* SectionObject = SectionModel ? SectionModel->GetSection() : nullptr;
|
|
if (!SectionModel || !SectionObject)
|
|
{
|
|
return LayerId;
|
|
}
|
|
|
|
TSharedPtr<FTrackAreaViewModel> TrackAreaViewModel = GetTrackAreaViewModel();
|
|
const ISequencerEditTool* EditTool = TrackAreaViewModel->GetEditTool();
|
|
TSharedPtr<ITrackAreaHotspot> Hotspot = EditTool ? EditTool->GetDragHotspot() : nullptr;
|
|
if (!Hotspot)
|
|
{
|
|
Hotspot = TrackAreaViewModel->GetHotspot();
|
|
}
|
|
|
|
UMovieSceneTrack* Track = SectionObject->GetTypedOuter<UMovieSceneTrack>();
|
|
|
|
FGuid BindingID;
|
|
if (TSharedPtr<IObjectBindingExtension> ObjectBindingExtension = SectionModel->FindAncestorOfType<IObjectBindingExtension>())
|
|
{
|
|
BindingID = ObjectBindingExtension->GetObjectGuid();
|
|
}
|
|
const UMovieSceneCondition* Condition = MovieSceneHelpers::GetSequenceCondition(Track, SectionObject);
|
|
|
|
const bool bConditionPasses = !Condition || MovieSceneHelpers::EvaluateSequenceCondition(BindingID, GetSequencer().GetFocusedTemplateID(), Condition, Track, GetSequencer().GetSharedPlaybackState());
|
|
const bool bTrackDisabled = Track && (Track->IsEvalDisabled() || Track->IsRowEvalDisabled(SectionObject->GetRowIndex()));
|
|
const bool bEnabled = bParentEnabled && SectionObject->IsActive() && bConditionPasses && !(bTrackDisabled);
|
|
|
|
const ESlateDrawEffect DrawEffects = bEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect;
|
|
|
|
FGeometry SectionGeometry = MakeHandlesGeometry(AllottedGeometry);
|
|
|
|
FSequencerSectionPainterImpl Painter(GetSequencer(), TrackAreaViewModel, SectionModel, OutDrawElements, SectionGeometry, *this);
|
|
|
|
FGeometry PaintSpaceParentGeometry = ParentGeometry;
|
|
PaintSpaceParentGeometry.AppendTransform(FSlateLayoutTransform(Inverse(Args.GetWindowToDesktopTransform())));
|
|
|
|
Painter.ParentClippingRect = PaintSpaceParentGeometry.GetLayoutBoundingRect();
|
|
|
|
// Clip vertically
|
|
Painter.ParentClippingRect.Top = FMath::Max(Painter.ParentClippingRect.Top, MyCullingRect.Top);
|
|
Painter.ParentClippingRect.Bottom = FMath::Min(Painter.ParentClippingRect.Bottom, MyCullingRect.Bottom);
|
|
|
|
Painter.SectionClippingRect = Painter.SectionGeometry.GetLayoutBoundingRect().InsetBy(FMargin(1.f)).IntersectionWith(Painter.ParentClippingRect);
|
|
|
|
Painter.LayerId = LayerId;
|
|
Painter.bParentEnabled = bEnabled;
|
|
Painter.bIsHighlighted = IsSectionHighlighted(SectionObject, Hotspot);
|
|
if (UMovieSceneSubSection* SubSection = Cast<UMovieSceneSubSection>(SectionObject))
|
|
{
|
|
if (( SubSection->GetNetworkMask() & GetSequencer().GetEvaluationTemplate().GetEmulatedNetworkMask() ) == EMovieSceneServerClientMask::None)
|
|
{
|
|
Painter.GhostAlpha = .3f;
|
|
}
|
|
}
|
|
|
|
Painter.bIsSelected = GetSequencer().GetSelection().TrackArea.IsSelected(SectionModel);
|
|
|
|
// Ask the interface to draw the section
|
|
LayerId = SectionInterface->OnPaintSection(Painter);
|
|
|
|
FLinearColor SelectionColor = FAppStyle::GetSlateColor(SequencerSectionConstants::SelectionColorName).GetColor(FWidgetStyle());
|
|
|
|
LayerId = SCompoundWidget::OnPaint( Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bEnabled );
|
|
|
|
// We paint section handles after channels so they draw on top, but we allow some leeway for channels to paint on a higher layer than they
|
|
// reported. This enables us to still draw and interact with keys that overlap section handles, even if we drew the handles chronologically later
|
|
DrawSectionHandles(AllottedGeometry, OutDrawElements, LayerId, DrawEffects, SelectionColor, Hotspot);
|
|
|
|
Painter.LayerId = (++LayerId);
|
|
PaintEasingHandles( Painter, SelectionColor, Hotspot );
|
|
|
|
// Artificially increase the layer now to ensure that we are now painting above keys now
|
|
LayerId += 10;
|
|
|
|
// --------------------------------------------
|
|
// Draw section selection tint
|
|
const float SectionThrobValue = GetSectionSelectionThrobValue();
|
|
if (SectionThrobValue != 0.f && GetSequencer().GetSelection().TrackArea.IsSelected(SectionModel))
|
|
{
|
|
static const FName BackgroundTrackTintBrushName("Sequencer.Section.BackgroundTint");
|
|
|
|
FSlateDrawElement::MakeBox(
|
|
OutDrawElements,
|
|
LayerId++,
|
|
AllottedGeometry.ToPaintGeometry(),
|
|
FAppStyle::GetBrush(BackgroundTrackTintBrushName),
|
|
DrawEffects,
|
|
SelectionColor.CopyWithNewOpacity(SectionThrobValue)
|
|
);
|
|
}
|
|
|
|
LayerId = Painter.LayerId;
|
|
|
|
// Section name with drop shadow
|
|
FText SectionTitle = SectionInterface->GetSectionTitle();
|
|
FMargin ContentPadding = SectionInterface->GetContentPadding();
|
|
|
|
const int32 EaseInAmount = SectionObject->Easing.GetEaseInDuration();
|
|
if (EaseInAmount > 0)
|
|
{
|
|
ContentPadding.Left += Painter.GetTimeConverter().FrameToPixel(EaseInAmount) - Painter.GetTimeConverter().FrameToPixel(0);
|
|
}
|
|
|
|
if (!SectionTitle.IsEmpty())
|
|
{
|
|
TViewModelPtr<IGeometryExtension> Geometry = SectionModel->FindAncestorOfType<IOutlinerExtension>().ImplicitCast();
|
|
const float AllocatedFontHeight = (Geometry ? Geometry->GetVirtualGeometry().GetHeight() : SectionGeometry.Size.Y) - 4.f; // 2px minimum padding
|
|
|
|
// Align the section title within the actual track area geometry, excluding any expanded channels
|
|
FSlateClippingZone ClippingZone(Painter.SectionClippingRect);
|
|
OutDrawElements.PushClip(ClippingZone);
|
|
|
|
FVector2D TopLeft = SectionGeometry.AbsoluteToLocal(Painter.SectionClippingRect.GetTopLeft()) + FVector2D(1.f, -1.f);
|
|
FSlateFontInfo FontInfo = FAppStyle::GetFontStyle("NormalFont");
|
|
|
|
TSharedRef<FSlateFontCache> FontCache = FSlateApplication::Get().GetRenderer()->GetFontCache();
|
|
|
|
auto GetFontHeight = [&]
|
|
{
|
|
return FontCache->GetMaxCharacterHeight(FontInfo, 1.f) + FontCache->GetBaseline(FontInfo, 1.f);
|
|
};
|
|
while (GetFontHeight() > AllocatedFontHeight && FontInfo.Size > 7.f)
|
|
{
|
|
FontInfo.Size = FMath::Max(FMath::FloorToInt(FontInfo.Size - 6.f), 7.f);
|
|
}
|
|
|
|
float TitlePosition = ContentPadding.Top;
|
|
if (FontInfo.Size + ContentPadding.Top + ContentPadding.Bottom > AllocatedFontHeight)
|
|
{
|
|
TitlePosition = (AllocatedFontHeight - FontInfo.Size) * 0.5f;
|
|
}
|
|
|
|
// Drop shadow
|
|
FSlateDrawElement::MakeText(
|
|
OutDrawElements,
|
|
LayerId,
|
|
SectionGeometry.MakeChild(
|
|
FVector2D(SectionGeometry.Size.X, GetFontHeight()),
|
|
FSlateLayoutTransform(TopLeft + FVector2D(ContentPadding.Left, TitlePosition) + FVector2D(1.f, 1.f))
|
|
).ToPaintGeometry(),
|
|
SectionTitle,
|
|
FontInfo,
|
|
DrawEffects,
|
|
FLinearColor(0, 0, 0, .5f * Painter.GhostAlpha)
|
|
);
|
|
|
|
FSlateDrawElement::MakeText(
|
|
OutDrawElements,
|
|
LayerId,
|
|
SectionGeometry.MakeChild(
|
|
FVector2D(SectionGeometry.Size.X, GetFontHeight()),
|
|
FSlateLayoutTransform(TopLeft + FVector2D(ContentPadding.Left, TitlePosition))
|
|
).ToPaintGeometry(),
|
|
SectionTitle,
|
|
FontInfo,
|
|
DrawEffects,
|
|
FColor(192, 192, 192, static_cast<uint8>(Painter.GhostAlpha * 255))
|
|
);
|
|
|
|
OutDrawElements.PopClip();
|
|
}
|
|
|
|
++LayerId;
|
|
|
|
TOptional<FFrameTime> SectionTime = SectionInterface->GetSectionTime(Painter);
|
|
if (SectionTime.IsSet())
|
|
{
|
|
TSharedPtr<INumericTypeInterface<double>> NumericTypeInterface = GetSequencer().GetNumericTypeInterface(ENumericIntent::Position);
|
|
DrawFrameTimeHint(Painter, GetSequencer().GetLocalTime().Time, SectionTime.GetValue(), NumericTypeInterface, LayerId);
|
|
}
|
|
|
|
++LayerId;
|
|
|
|
if (Painter.bClipRectEnabled)
|
|
{
|
|
OutDrawElements.PopClip();
|
|
}
|
|
|
|
return LayerId;
|
|
}
|
|
|
|
void SSequencerSection::PaintEasingHandles( FSequencerSectionPainter& InPainter, FLinearColor SelectionColor, TSharedPtr<ITrackAreaHotspot> Hotspot ) const
|
|
{
|
|
if (!SectionInterface->GetSectionObject()->GetBlendType().IsValid())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (Hotspot && !Hotspot->IsA<FSectionHotspotBase>())
|
|
{
|
|
return;
|
|
}
|
|
|
|
TSharedPtr<FSectionModel> SectionModel = WeakSectionModel.Pin();
|
|
TSharedPtr<FSharedViewModelData> SharedModelData = SectionModel ? SectionModel->GetSharedData() : nullptr;
|
|
TSharedPtr<FEditorSharedViewModelData> EditorSharedModelData = SharedModelData ? SharedModelData->CastThisShared<FEditorSharedViewModelData>() : nullptr;
|
|
if (!SectionModel || !EditorSharedModelData)
|
|
{
|
|
return;
|
|
}
|
|
|
|
TArray<TSharedPtr<FSectionModel>> AllUnderlappingSections;
|
|
if (IsSectionHighlighted(SectionInterface->GetSectionObject(), Hotspot))
|
|
{
|
|
if (SectionModel)
|
|
{
|
|
AllUnderlappingSections.Add(SectionModel);
|
|
}
|
|
}
|
|
|
|
for (const FOverlappingSections& Segment : UnderlappingSegments)
|
|
{
|
|
for (const TWeakPtr<FSectionModel>& Section : Segment.Sections)
|
|
{
|
|
UMovieSceneSection* SectionObject = Section.Pin()->GetSection();
|
|
if (IsSectionHighlighted(SectionObject, Hotspot))
|
|
{
|
|
AllUnderlappingSections.AddUnique(Section.Pin());
|
|
}
|
|
}
|
|
}
|
|
|
|
FTimeToPixel TimeToPixelConverter = InPainter.GetTimeConverter();
|
|
for (const TSharedPtr<FSectionModel>& UnderlappingSectionModel : AllUnderlappingSections)
|
|
{
|
|
UMovieSceneSection* UnderlappingSectionObj = UnderlappingSectionModel->GetSection();
|
|
if (UnderlappingSectionObj->GetRange() == TRange<FFrameNumber>::All())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
bool bDrawThisSectionsHandles = true;
|
|
bool bLeftHandleActive = false;
|
|
bool bRightHandleActive = false;
|
|
|
|
// Get the hovered/selected state for the section handles from the hotspot
|
|
if (Hotspot)
|
|
{
|
|
if (const FSectionEasingHandleHotspot* EasingHotspot = Hotspot->CastThis<FSectionEasingHandleHotspot>())
|
|
{
|
|
bDrawThisSectionsHandles = (EasingHotspot->WeakSectionModel.Pin() == UnderlappingSectionModel);
|
|
bLeftHandleActive = EasingHotspot->HandleType == ESequencerEasingType::In;
|
|
bRightHandleActive = EasingHotspot->HandleType == ESequencerEasingType::Out;
|
|
}
|
|
else if (const FSectionEasingAreaHotspot* EasingAreaHotspot = Hotspot->CastThis<FSectionEasingAreaHotspot>())
|
|
{
|
|
for (const FEasingAreaHandle& Easing : EasingAreaHotspot->Easings)
|
|
{
|
|
TSharedPtr<FSectionModel> EasingSectionModel = Easing.WeakSectionModel.Pin();
|
|
if (EasingSectionModel && EasingSectionModel->GetSection() == UnderlappingSectionObj)
|
|
{
|
|
if (Easing.EasingType == ESequencerEasingType::In)
|
|
{
|
|
bLeftHandleActive = true;
|
|
}
|
|
else
|
|
{
|
|
bRightHandleActive = true;
|
|
}
|
|
|
|
if (bLeftHandleActive && bRightHandleActive)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const UMovieSceneTrack* Track = UnderlappingSectionObj->GetTypedOuter<UMovieSceneTrack>();
|
|
FMovieSceneSupportsEasingParams SupportsEasingParams(UnderlappingSectionObj);
|
|
EMovieSceneTrackEasingSupportFlags EasingSupportFlags = Track->SupportsEasing(SupportsEasingParams);
|
|
|
|
if (!bDrawThisSectionsHandles || !EnumHasAnyFlags(EasingSupportFlags, EMovieSceneTrackEasingSupportFlags::ManualEasing))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// We only draw the corner radius for the easing handle when it's at the same place as the grip. Otherwise we
|
|
// draw a straight triangle.
|
|
const float HandleCornerRadius = 2.f;
|
|
const float MinHandleSize = 8.f;
|
|
|
|
const ESlateDrawEffect DrawEffects = InPainter.bParentEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect;
|
|
const bool bIsSectionToKey = Track->GetAllSections().Num() > 1 && IsSectionToKey(Track, UnderlappingSectionObj);
|
|
|
|
// If this is the section to key, we draw the easing handle in the same bright green color as the border
|
|
// outline. We also make the handle a bit bigger, otherwise that border being drawn on top makes it look
|
|
// smaller.
|
|
// If there isn't any "section to key" border, we just draw the handle white.
|
|
const FLinearColor InactiveHandleColor = bIsSectionToKey ? FStyleColors::Success.GetColor(FWidgetStyle()) : FStyleColors::AccentYellow.GetColor(FWidgetStyle());
|
|
const float HalfSectionHeight = SectionInterface->GetSectionHeight(EditorSharedModelData->GetEditor()->GetViewDensity()) / 2.f;
|
|
const float HandleSize = FMath::Max(MinHandleSize, FMath::Min(HalfSectionHeight, SectionInterface->GetSectionGripSize())) + (bIsSectionToKey ? 3.f : 0.f);
|
|
|
|
const FSlateBrush* HandleBrush = FAppStyle::GetBrush("Sequencer.Section.EasingHandle");
|
|
FSlateResourceHandle HandleResource = FSlateApplication::Get().GetRenderer()->GetResourceHandle(*HandleBrush);
|
|
|
|
const FSlateRenderTransform& HandleRenderTransform = InPainter.SectionGeometry.GetAccumulatedRenderTransform();
|
|
|
|
if (UnderlappingSectionObj->HasStartFrame() && EnumHasAllFlags(EasingSupportFlags, EMovieSceneTrackEasingSupportFlags::ManualEaseIn))
|
|
{
|
|
TRange<FFrameNumber> EaseInRange = UnderlappingSectionObj->GetEaseInRange();
|
|
const bool bHasEaseIn = !EaseInRange.IsEmpty();
|
|
FFrameNumber HandleFrame = bHasEaseIn ? UE::MovieScene::DiscreteExclusiveUpper(EaseInRange) : UnderlappingSectionObj->GetInclusiveStartFrame();
|
|
const float HandlePos(TimeToPixel->FrameToPixel(HandleFrame) - HandleOffsetPx);
|
|
FColor HandleColor = (bLeftHandleActive ? SelectionColor : InactiveHandleColor).ToFColorSRGB();
|
|
|
|
TArray<FSlateVertex> Verts;
|
|
Verts.Add(FSlateVertex::Make<ESlateVertexRounding::Disabled>(HandleRenderTransform, FVector2f(HandlePos, 0.f), FVector2f(0.f, 0.f), HandleColor));
|
|
Verts.Add(FSlateVertex::Make<ESlateVertexRounding::Disabled>(HandleRenderTransform, FVector2f(HandlePos, HandleCornerRadius), FVector2f(0.f, 0.1f), HandleColor));
|
|
Verts.Add(FSlateVertex::Make<ESlateVertexRounding::Disabled>(HandleRenderTransform, FVector2f(HandlePos + HandleCornerRadius, 0.f), FVector2f(0.1f, 0.f), HandleColor));
|
|
Verts.Add(FSlateVertex::Make<ESlateVertexRounding::Disabled>(HandleRenderTransform, FVector2f(HandlePos + HandleSize, 0.f), FVector2f(1.f, 0.f), HandleColor));
|
|
Verts.Add(FSlateVertex::Make<ESlateVertexRounding::Disabled>(HandleRenderTransform, FVector2f(HandlePos, HandleSize), FVector2f(0.f, 1.f), HandleColor));
|
|
|
|
TArray<SlateIndex> Indices;
|
|
if (bHasEaseIn)
|
|
{
|
|
Indices.Add(0); Indices.Add(3); Indices.Add(4);
|
|
}
|
|
else
|
|
{
|
|
Indices.Add(1); Indices.Add(2); Indices.Add(3);
|
|
Indices.Add(1); Indices.Add(3); Indices.Add(4);
|
|
}
|
|
|
|
FSlateDrawElement::MakeCustomVerts(
|
|
InPainter.DrawElements,
|
|
InPainter.LayerId,
|
|
HandleResource,
|
|
Verts,
|
|
Indices,
|
|
nullptr,
|
|
0,
|
|
0,
|
|
DrawEffects | ESlateDrawEffect::PreMultipliedAlpha
|
|
);
|
|
}
|
|
|
|
if (UnderlappingSectionObj->HasEndFrame() && EnumHasAllFlags(EasingSupportFlags, EMovieSceneTrackEasingSupportFlags::ManualEaseOut))
|
|
{
|
|
TRange<FFrameNumber> EaseOutRange = UnderlappingSectionObj->GetEaseOutRange();
|
|
const bool bHasEaseOut = !EaseOutRange.IsEmpty();
|
|
FFrameNumber HandleFrame = bHasEaseOut ? UE::MovieScene::DiscreteInclusiveLower(EaseOutRange) : UnderlappingSectionObj->GetExclusiveEndFrame();
|
|
const float HandlePos(TimeToPixel->FrameToPixel(HandleFrame) + HandleOffsetPx);
|
|
const FColor HandleColor = (bRightHandleActive ? SelectionColor : InactiveHandleColor).ToFColorSRGB();
|
|
|
|
TArray<FSlateVertex> Verts;
|
|
Verts.Add(FSlateVertex::Make<ESlateVertexRounding::Disabled>(HandleRenderTransform, FVector2f(HandlePos, 0.f), FVector2f(1.f, 0.f), HandleColor));
|
|
Verts.Add(FSlateVertex::Make<ESlateVertexRounding::Disabled>(HandleRenderTransform, FVector2f(HandlePos - HandleCornerRadius, 0.f), FVector2f(0.9f, 0.f), HandleColor));
|
|
Verts.Add(FSlateVertex::Make<ESlateVertexRounding::Disabled>(HandleRenderTransform, FVector2f(HandlePos, HandleCornerRadius), FVector2f(1.f, 0.1f), HandleColor));
|
|
Verts.Add(FSlateVertex::Make<ESlateVertexRounding::Disabled>(HandleRenderTransform, FVector2f(HandlePos, HandleSize), FVector2f(1.f, 1.f), HandleColor));
|
|
Verts.Add(FSlateVertex::Make<ESlateVertexRounding::Disabled>(HandleRenderTransform, FVector2f(HandlePos - HandleSize, 0.f), FVector2f(0.f, 0.f), HandleColor));
|
|
|
|
TArray<SlateIndex> Indices;
|
|
if (bHasEaseOut)
|
|
{
|
|
Indices.Add(0); Indices.Add(3); Indices.Add(4);
|
|
}
|
|
else
|
|
{
|
|
Indices.Add(4); Indices.Add(1); Indices.Add(2);
|
|
Indices.Add(4); Indices.Add(2); Indices.Add(3);
|
|
}
|
|
|
|
FSlateDrawElement::MakeCustomVerts(
|
|
InPainter.DrawElements,
|
|
InPainter.LayerId,
|
|
HandleResource,
|
|
Verts,
|
|
Indices,
|
|
nullptr,
|
|
0,
|
|
0,
|
|
DrawEffects | ESlateDrawEffect::PreMultipliedAlpha
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void SSequencerSection::DrawSectionHandles( const FGeometry& AllottedGeometry, FSlateWindowElementList& OutDrawElements, int32 LayerId, ESlateDrawEffect DrawEffects, FLinearColor SelectionColor, TSharedPtr<ITrackAreaHotspot> Hotspot ) const
|
|
{
|
|
UMovieSceneSection* ThisSection = SectionInterface->GetSectionObject();
|
|
if (!ThisSection)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (Hotspot && !Hotspot->IsA<FSectionHotspotBase>())
|
|
{
|
|
return;
|
|
}
|
|
|
|
TOptional<FSlateClippingState> PreviousClipState = OutDrawElements.GetClippingState();
|
|
if (PreviousClipState.IsSet())
|
|
{
|
|
OutDrawElements.PopClip();
|
|
}
|
|
|
|
OutDrawElements.PushClip(FSlateClippingZone(AllottedGeometry.GetLayoutBoundingRect()));
|
|
|
|
TArray<TSharedPtr<FSectionModel>> AllUnderlappingSections;
|
|
if (IsSectionHighlighted(SectionInterface->GetSectionObject(), Hotspot))
|
|
{
|
|
if (TSharedPtr<FSectionModel> SectionModel = WeakSectionModel.Pin())
|
|
{
|
|
AllUnderlappingSections.Add(SectionModel);
|
|
}
|
|
}
|
|
|
|
for (const FOverlappingSections& Segment : UnderlappingSegments)
|
|
{
|
|
for (const TWeakPtr<FSectionModel>& Section : Segment.Sections)
|
|
{
|
|
UMovieSceneSection* SectionObject = Section.Pin()->GetSection();
|
|
if (IsSectionHighlighted(SectionObject, Hotspot))
|
|
{
|
|
AllUnderlappingSections.AddUnique(Section.Pin());
|
|
}
|
|
}
|
|
}
|
|
|
|
FGeometry SectionGeometry = MakeHandlesGeometry(AllottedGeometry);
|
|
|
|
for (TSharedPtr<FSectionModel> SectionModel : AllUnderlappingSections)
|
|
{
|
|
UMovieSceneSection* UnderlappingSectionObj = SectionModel->GetSection();
|
|
TSharedPtr<ISequencerSection> UnderlappingSection = SectionModel->GetSectionInterface();
|
|
if (!UnderlappingSection->SectionIsResizable() || UnderlappingSectionObj->GetRange() == TRange<FFrameNumber>::All())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
bool bDrawThisSectionsHandles = (UnderlappingSectionObj == ThisSection && HandleOffsetPx != 0) || IsSectionHighlighted(UnderlappingSectionObj, Hotspot);
|
|
bool bLeftHandleActive = false;
|
|
bool bRightHandleActive = false;
|
|
|
|
// Get the hovered/selected state for the section handles from the hotspot
|
|
if (TSharedPtr<FSectionResizeHotspot> ResizeHotspot = HotspotCast<FSectionResizeHotspot>(Hotspot))
|
|
{
|
|
if (ResizeHotspot->WeakSectionModel.Pin() == SectionModel)
|
|
{
|
|
bDrawThisSectionsHandles = true;
|
|
bLeftHandleActive = ResizeHotspot->HandleType == FSectionResizeHotspot::Left;
|
|
bRightHandleActive = ResizeHotspot->HandleType == FSectionResizeHotspot::Right;
|
|
}
|
|
else
|
|
{
|
|
bDrawThisSectionsHandles = false;
|
|
}
|
|
}
|
|
|
|
if (!bDrawThisSectionsHandles)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const float ThisHandleOffset = UnderlappingSectionObj == ThisSection ? HandleOffsetPx : 0.f;
|
|
FVector2D GripSize( UnderlappingSection->GetSectionGripSize(), UnderlappingSection->GetSectionGripHeight(AllottedGeometry.Size.Y) );
|
|
|
|
float Opacity = 0.85f;
|
|
if (ThisHandleOffset != 0)
|
|
{
|
|
Opacity = FMath::Clamp(Opacity + ThisHandleOffset / GripSize.X * (1.f-Opacity), Opacity, 1.f);
|
|
}
|
|
|
|
const FSlateBrush* LeftGripBrush = FAppStyle::GetBrush("Sequencer.Section.GripLeft");
|
|
const FSlateBrush* RightGripBrush = FAppStyle::GetBrush("Sequencer.Section.GripRight");
|
|
|
|
// Left Grip
|
|
if (UnderlappingSectionObj->HasStartFrame())
|
|
{
|
|
FGeometry SectionRectLeft = SectionGeometry.MakeChild(
|
|
GripSize,
|
|
FSlateLayoutTransform(FVector2D( TimeToPixel->FrameToPixel(UnderlappingSectionObj->GetInclusiveStartFrame()) - ThisHandleOffset, 0.f ))
|
|
);
|
|
FSlateDrawElement::MakeBox
|
|
(
|
|
OutDrawElements,
|
|
LayerId,
|
|
SectionRectLeft.ToPaintGeometry(),
|
|
LeftGripBrush,
|
|
DrawEffects,
|
|
(bLeftHandleActive ? SelectionColor : LeftGripBrush->GetTint(FWidgetStyle())).CopyWithNewOpacity(Opacity)
|
|
);
|
|
}
|
|
|
|
// Right Grip
|
|
if (UnderlappingSectionObj->HasEndFrame())
|
|
{
|
|
FGeometry SectionRectRight = SectionGeometry.MakeChild(
|
|
GripSize,
|
|
FSlateLayoutTransform(FVector2D( TimeToPixel->FrameToPixel(UnderlappingSectionObj->GetExclusiveEndFrame()) - UnderlappingSection->GetSectionGripSize() + ThisHandleOffset, 0 ))
|
|
);
|
|
FSlateDrawElement::MakeBox
|
|
(
|
|
OutDrawElements,
|
|
LayerId,
|
|
SectionRectRight.ToPaintGeometry(),
|
|
RightGripBrush,
|
|
DrawEffects,
|
|
(bRightHandleActive ? SelectionColor : RightGripBrush->GetTint(FWidgetStyle())).CopyWithNewOpacity(Opacity)
|
|
);
|
|
}
|
|
}
|
|
|
|
OutDrawElements.PopClip();
|
|
if (PreviousClipState.IsSet())
|
|
{
|
|
OutDrawElements.GetClippingManager().PushClippingState(PreviousClipState.GetValue());
|
|
}
|
|
}
|
|
|
|
void SSequencerSection::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime )
|
|
{
|
|
UMovieSceneSection* Section = SectionInterface->GetSectionObject();
|
|
TSharedPtr<FSequencer> SequencerPtr = Sequencer.Pin();
|
|
TSharedPtr<FSectionModel> SectionModel = WeakSectionModel.Pin();
|
|
|
|
if (!Section || !SequencerPtr || !SectionModel)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (GetVisibility() == EVisibility::Visible)
|
|
{
|
|
if (Section->HasStartFrame() && Section->HasEndFrame())
|
|
{
|
|
constexpr float MinSectionWidth = 1.f;
|
|
|
|
FTimeToPixel TimeToPixelConverter(ParentGeometry, GetSequencer().GetViewRange(), Section->GetTypedOuter<UMovieScene>()->GetTickResolution());
|
|
const FFrameNumber SectionLength = UE::MovieScene::DiscreteSize(Section->ComputeEffectiveRange());
|
|
const float SectionWidth = TimeToPixelConverter.FrameDeltaToPixel(SectionLength);
|
|
|
|
const float GripSize = SectionInterface->GetSectionGripSize();
|
|
const float FinalSectionWidth = FMath::Max(MinSectionWidth + GripSize * 2.f, SectionWidth);
|
|
HandleOffsetPx = FMath::RoundToFloat((FinalSectionWidth - SectionWidth) * 0.5f);
|
|
}
|
|
else
|
|
{
|
|
HandleOffsetPx = 0;
|
|
}
|
|
|
|
FGeometry SectionGeometry = MakeSectionGeometryWithoutHandles(AllottedGeometry);
|
|
|
|
FTrackLaneVirtualAlignment VirtualAlignment = SectionModel->ArrangeVirtualTrackLaneView();
|
|
*TimeToPixel = *WeakOwningTrackLane.Pin()->GetTrackAreaView()->GetTimeToPixel(VirtualAlignment.ViewSpaceID);
|
|
if (Section->HasStartFrame())
|
|
{
|
|
*TimeToPixel = TimeToPixel->RelativeTo(Section->GetInclusiveStartFrame());
|
|
}
|
|
SectionInterface->Tick(SectionGeometry, ParentGeometry, InCurrentTime, InDeltaTime);
|
|
}
|
|
else
|
|
{
|
|
FTrackLaneVirtualAlignment VirtualAlignment = SectionModel->ArrangeVirtualTrackLaneView();
|
|
*TimeToPixel = *WeakOwningTrackLane.Pin()->GetTrackAreaView()->GetTimeToPixel(VirtualAlignment.ViewSpaceID);
|
|
if (Section->HasStartFrame())
|
|
{
|
|
*TimeToPixel = TimeToPixel->RelativeTo(Section->GetInclusiveStartFrame());
|
|
}
|
|
}
|
|
}
|
|
|
|
void SSequencerSection::AddChildView(TSharedPtr<ITrackLaneWidget> ChildWidget, TWeakPtr<STrackLane> InWeakOwningLane)
|
|
{
|
|
ChildLaneWidgets->AddWeakWidget(ChildWidget, InWeakOwningLane);
|
|
}
|
|
|
|
void SSequencerSection::OnModifiedIndirectly(UMovieSceneSignedObject* Object)
|
|
{
|
|
if (Object->IsA<UMovieSceneSection>())
|
|
{
|
|
UpdateUnderlappingSegments();
|
|
}
|
|
}
|
|
|
|
TSharedRef<FTimeToPixel> SSequencerSection::GetTimeToPixel() const
|
|
{
|
|
return TimeToPixel.ToSharedRef();
|
|
}
|
|
|
|
FTimeToPixel SSequencerSection::GetScreenSpace(const FGuid& ViewSpaceID) const
|
|
{
|
|
using namespace UE::MovieScene;
|
|
|
|
TSharedPtr<STrackLane> TrackLane = WeakOwningTrackLane.Pin();
|
|
TSharedPtr<STrackAreaView> TrackArea = TrackLane ? TrackLane->GetTrackAreaView() : nullptr;
|
|
if (TrackArea)
|
|
{
|
|
TSharedPtr<FTimeToPixel> Space = TrackArea->GetTimeToPixel(ViewSpaceID);
|
|
|
|
TSharedPtr<FSectionModel> Section = WeakSectionModel.Pin();
|
|
if (Section)
|
|
{
|
|
TRange<FFrameNumber> Range = Section->GetRange();
|
|
if (Range.GetLowerBound().IsClosed())
|
|
{
|
|
return Space->RelativeTo(DiscreteInclusiveLower(Range));
|
|
}
|
|
}
|
|
|
|
return *Space;
|
|
}
|
|
|
|
return *TimeToPixel;
|
|
}
|
|
|
|
|
|
FReply SSequencerSection::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
|
{
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
FGeometry SSequencerSection::MakeSectionGeometryWithoutHandles(const FGeometry& AllottedGeometry) const
|
|
{
|
|
const FVector2f LocalSize = AllottedGeometry.GetLocalSize();
|
|
|
|
FVector2f SectionSizeWithoutHandles = LocalSize - FVector2f(HandleOffsetPx * 2.f, 0.0f);
|
|
SectionSizeWithoutHandles.X = FMath::Max(1.f, SectionSizeWithoutHandles.X);
|
|
|
|
return AllottedGeometry.MakeChild(
|
|
SectionSizeWithoutHandles,
|
|
FSlateLayoutTransform(FVector2D(HandleOffsetPx, 0))
|
|
);
|
|
}
|
|
|
|
FGeometry SSequencerSection::MakeHandlesGeometry(const FGeometry& AllottedGeometry) const
|
|
{
|
|
const FVector2f LocalSize = AllottedGeometry.GetLocalSize();
|
|
|
|
FVector2f SectionSizeWithoutHandles = LocalSize - FVector2f(HandleOffsetPx * 2.f, 0.f);
|
|
SectionSizeWithoutHandles.X = FMath::Max(1.f, SectionSizeWithoutHandles.X);
|
|
SectionSizeWithoutHandles.Y = FMath::Max(1.f, SectionInterface->GetSectionGripHeight(SectionSizeWithoutHandles.Y));
|
|
|
|
return AllottedGeometry.MakeChild(
|
|
SectionSizeWithoutHandles,
|
|
FSlateLayoutTransform(FVector2D(HandleOffsetPx, 0))
|
|
);
|
|
}
|
|
|
|
void SSequencerSection::UpdateUnderlappingSegments()
|
|
{
|
|
TSharedPtr<FSectionModel> SectionModel = WeakSectionModel.Pin();
|
|
if (SectionModel)
|
|
{
|
|
UnderlappingSegments = SectionModel->GetUnderlappingSections();
|
|
UnderlappingEasingSegments = SectionModel->GetEasingSegments();
|
|
}
|
|
else
|
|
{
|
|
UnderlappingSegments.Reset();
|
|
UnderlappingEasingSegments.Reset();
|
|
}
|
|
}
|
|
|
|
FReply SSequencerSection::OnMouseButtonDoubleClick( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
|
|
{
|
|
TSharedPtr<FSectionModel> SectionModel = WeakSectionModel.Pin();
|
|
if (!SectionModel)
|
|
{
|
|
return FReply::Handled();
|
|
}
|
|
|
|
if( MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton )
|
|
{
|
|
FReply Reply = SectionInterface->OnSectionDoubleClicked( MyGeometry, MouseEvent );
|
|
if (!Reply.IsEventHandled())
|
|
{
|
|
// Find the object binding this node is underneath
|
|
FGuid ObjectBinding;
|
|
if (TSharedPtr<IObjectBindingExtension> ObjectBindingExtension = SectionModel->FindAncestorOfType<IObjectBindingExtension>())
|
|
{
|
|
ObjectBinding = ObjectBindingExtension->GetObjectGuid();
|
|
}
|
|
|
|
Reply = SectionInterface->OnSectionDoubleClicked(MyGeometry, MouseEvent, ObjectBinding);
|
|
}
|
|
|
|
if (Reply.IsEventHandled())
|
|
{
|
|
return Reply;
|
|
}
|
|
|
|
GetSequencer().ZoomToFit();
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
|
|
FReply SSequencerSection::OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
|
{
|
|
if (MouseEvent.GetCursorDelta().Size() > 0)
|
|
{
|
|
if (CheckForEasingHandleInteraction(MouseEvent, MyGeometry) ||
|
|
CheckForEdgeInteraction(MouseEvent, MyGeometry) ||
|
|
CheckForEasingAreaInteraction(MouseEvent, MyGeometry) ||
|
|
CheckForSectionInteraction(MouseEvent, MyGeometry))
|
|
{
|
|
// Do nothing: we always want to return FReply::Unhandled because we're just setting up
|
|
// hotspots here. STrackAreaView will actually handle the mouse movement event, including
|
|
// calling it on the active hotspot, doing panning, and more.
|
|
}
|
|
}
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
FReply SSequencerSection::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
|
{
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
void SSequencerSection::OnMouseLeave( const FPointerEvent& MouseEvent )
|
|
{
|
|
SCompoundWidget::OnMouseLeave( MouseEvent );
|
|
|
|
TSharedPtr<FTrackAreaViewModel> TrackAreaViewModel = GetTrackAreaViewModel();
|
|
if (TrackAreaViewModel)
|
|
{
|
|
TrackAreaViewModel->SetHotspot(nullptr);
|
|
}
|
|
}
|
|
|
|
static float SectionThrobDurationSeconds = 1.f;
|
|
void SSequencerSection::ThrobSectionSelection(int32 ThrobCount)
|
|
{
|
|
SectionSelectionThrobEndTime = FPlatformTime::Seconds() + ThrobCount*SectionThrobDurationSeconds;
|
|
}
|
|
|
|
static float KeyThrobDurationSeconds = .5f;
|
|
void SSequencerSection::ThrobKeySelection(int32 ThrobCount)
|
|
{
|
|
KeySelectionThrobEndTime = FPlatformTime::Seconds() + ThrobCount*KeyThrobDurationSeconds;
|
|
}
|
|
|
|
float EvaluateThrob(float Alpha)
|
|
{
|
|
return .5f - FMath::Cos(FMath::Pow(Alpha, 0.5f) * 2 * PI) * .5f;
|
|
}
|
|
|
|
float SSequencerSection::GetSectionSelectionThrobValue()
|
|
{
|
|
double CurrentTime = FPlatformTime::Seconds();
|
|
|
|
if (SectionSelectionThrobEndTime > CurrentTime)
|
|
{
|
|
float Difference = SectionSelectionThrobEndTime - CurrentTime;
|
|
return EvaluateThrob(1.f - FMath::Fmod(Difference, SectionThrobDurationSeconds));
|
|
}
|
|
|
|
return 0.f;
|
|
}
|
|
|
|
float SSequencerSection::GetKeySelectionThrobValue()
|
|
{
|
|
double CurrentTime = FPlatformTime::Seconds();
|
|
|
|
if (KeySelectionThrobEndTime > CurrentTime)
|
|
{
|
|
float Difference = KeySelectionThrobEndTime - CurrentTime;
|
|
return EvaluateThrob(1.f - FMath::Fmod(Difference, KeyThrobDurationSeconds));
|
|
}
|
|
|
|
return 0.f;
|
|
}
|
|
|
|
bool SSequencerSection::IsSectionHighlighted(UMovieSceneSection* InSection, TSharedPtr<ITrackAreaHotspot> Hotspot)
|
|
{
|
|
if (!Hotspot)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (FKeyHotspot* KeyHotspot = Hotspot->CastThis<FKeyHotspot>())
|
|
{
|
|
for (const FSequencerSelectedKey& Key : KeyHotspot->Keys)
|
|
{
|
|
if (Key.Section == InSection)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else if (FSectionEasingAreaHotspot* EasingAreaHotspot = Hotspot->CastThis<FSectionEasingAreaHotspot>())
|
|
{
|
|
return EasingAreaHotspot->Contains(InSection);
|
|
}
|
|
else if (FSectionHotspotBase* SectionHotspot = Hotspot->CastThis<FSectionHotspotBase>())
|
|
{
|
|
if (TSharedPtr<FSectionModel> SectionModel = SectionHotspot->WeakSectionModel.Pin())
|
|
{
|
|
return SectionModel->GetSection() == InSection;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
} // namespace Sequencer
|
|
} // namespace UE
|