784 lines
26 KiB
C++
784 lines
26 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "MVVM/Views/SChannelView.h"
|
|
#include "MVVM/ViewModels/ChannelModel.h"
|
|
#include "MVVM/ViewModels/SectionModel.h"
|
|
#include "MVVM/ViewModels/SequenceModel.h"
|
|
#include "MVVM/ViewModels/TrackAreaViewModel.h"
|
|
#include "MVVM/ViewModels/SequencerEditorViewModel.h"
|
|
#include "MVVM/Views/STrackAreaView.h"
|
|
#include "MVVM/Extensions/IMutableExtension.h"
|
|
#include "MVVM/Selection/Selection.h"
|
|
|
|
#include "IKeyArea.h"
|
|
#include "MovieScene.h"
|
|
#include "Sequencer.h"
|
|
#include "SequencerHotspots.h"
|
|
#include "ISequencerEditTool.h"
|
|
#include "ISequencerSection.h"
|
|
#include "SequencerAddKeyOperation.h"
|
|
#include "SSequencerSection.h"
|
|
#include "SequencerChannelTraits.h"
|
|
|
|
#include "ScopedTransaction.h"
|
|
|
|
#include "Algo/Transform.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "SequencerChannelView"
|
|
|
|
namespace UE::Sequencer
|
|
{
|
|
|
|
/** Convert a SequencerCore FKeyRenderer hittest query to FSequencerSelectedKeys */
|
|
TArray<FSequencerSelectedKey> KeyRendererResultToSelectedKeys(TArrayView<const FKeyRenderer::FKeysForModel> InKeys, UMovieSceneSection* SectionObject)
|
|
{
|
|
TArray<FSequencerSelectedKey> SelectedKeys;
|
|
for (const FKeyRenderer::FKeysForModel& Result : InKeys)
|
|
{
|
|
TViewModelPtr<FChannelModel> Channel = Result.Model.ImplicitCast();
|
|
if (Channel && Result.Keys.Num() > 0)
|
|
{
|
|
SelectedKeys.Reserve(SelectedKeys.Num() + Result.Keys.Num());
|
|
for (FKeyHandle Key : Result.Keys)
|
|
{
|
|
SelectedKeys.Add(FSequencerSelectedKey(*SectionObject, Channel, Key));
|
|
}
|
|
}
|
|
}
|
|
return SelectedKeys;
|
|
}
|
|
|
|
|
|
/** Key renderer interface for defining selection and hover states. Should be removed in favor of SequencerCore knowing about selection in the future */
|
|
struct FSequencerKeyRendererInterface : IKeyRendererInterface
|
|
{
|
|
const TSet<FKeyHandle>* SelectedKeys;
|
|
const TMap<FKeyHandle, ESelectionPreviewState>* SelectionPreview;
|
|
const TSet<FKeyHandle>* HoveredKeys;
|
|
|
|
FSequencerKeyRendererInterface(FSequencer* Sequencer, TSharedPtr<ITrackAreaHotspot> Hotspot)
|
|
{
|
|
TSharedPtr<FKeyHotspot> KeyHotspot = HotspotCast<FKeyHotspot>(Hotspot);
|
|
|
|
SelectedKeys = &Sequencer->GetViewModel()->GetSelection()->KeySelection.GetSelected();
|
|
SelectionPreview = &Sequencer->GetSelectionPreview().GetDefinedKeyStates();
|
|
HoveredKeys = KeyHotspot ? &KeyHotspot->RawKeys : nullptr;
|
|
}
|
|
|
|
bool HasAnySelectedKeys() const override
|
|
{
|
|
return SelectedKeys->Num() != 0;
|
|
}
|
|
bool HasAnyPreviewSelectedKeys() const override
|
|
{
|
|
return SelectionPreview->Num() != 0;
|
|
}
|
|
bool HasAnyHoveredKeys() const override
|
|
{
|
|
return HoveredKeys && HoveredKeys->Num() != 0;
|
|
}
|
|
|
|
bool IsKeySelected(const TViewModelPtr<IKeyExtension>& InOwner, FKeyHandle InKey) const override
|
|
{
|
|
return SelectedKeys->Contains(InKey);
|
|
}
|
|
bool IsKeyHovered(const TViewModelPtr<IKeyExtension>& InOwner, FKeyHandle InKey) const override
|
|
{
|
|
return HoveredKeys ? HoveredKeys->Contains(InKey) : false;
|
|
}
|
|
EKeySelectionPreviewState GetPreviewSelectionState(const TViewModelPtr<IKeyExtension>& InOwner, FKeyHandle InKey) const override
|
|
{
|
|
if (const ESelectionPreviewState* State = SelectionPreview->Find(InKey))
|
|
{
|
|
switch(*State)
|
|
{
|
|
case ESelectionPreviewState::Undefined: return EKeySelectionPreviewState::Undefined;
|
|
case ESelectionPreviewState::Selected: return EKeySelectionPreviewState::Selected;
|
|
case ESelectionPreviewState::NotSelected: return EKeySelectionPreviewState::NotSelected;
|
|
}
|
|
}
|
|
|
|
return EKeySelectionPreviewState::Undefined;
|
|
}
|
|
};
|
|
|
|
|
|
FChannelViewKeyCachedState::FChannelViewKeyCachedState()
|
|
: ValidPlayRangeMin(TNumericLimits<int32>::Lowest())
|
|
, ValidPlayRangeMax(TNumericLimits<int32>::Max())
|
|
, VisibleRange(0, 1)
|
|
, KeySizePx(12, 12)
|
|
, SelectionSerial(0)
|
|
, SelectionPreviewHash(0)
|
|
, bShowCurve(false)
|
|
, bShowKeyBars(true)
|
|
, bCollapseChildren(false)
|
|
, bIsChannelHovered(false)
|
|
{}
|
|
|
|
FChannelViewKeyCachedState::FChannelViewKeyCachedState(TRange<FFrameTime> InVisibleRange, TSharedPtr<ITrackAreaHotspot> Hotspot, FViewModelPtr Model, FSequencer* Sequencer)
|
|
{
|
|
// 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> ValidKeyRange = Sequencer->GetSubSequenceRange().Get(Sequencer->GetFocusedMovieSceneSequence()->GetMovieScene()->GetPlaybackRange());
|
|
|
|
TViewModelPtr<FChannelModel> Channel = Model.ImplicitCast();
|
|
if (!Channel)
|
|
{
|
|
TOptional<FViewModelChildren> TopLevelChannels = Model->FindChildList(FTrackModel::GetTopLevelChannelType());
|
|
if (TopLevelChannels.IsSet())
|
|
{
|
|
Channel = TopLevelChannels->FindFirstChildOfType<FChannelModel>();
|
|
}
|
|
}
|
|
TViewModelPtr<FLinkedOutlinerExtension> Outliner = Model.ImplicitCast();
|
|
|
|
ValidPlayRangeMin = UE::MovieScene::DiscreteInclusiveLower(ValidKeyRange);
|
|
ValidPlayRangeMax = UE::MovieScene::DiscreteExclusiveUpper(ValidKeyRange);
|
|
VisibleRange = InVisibleRange;
|
|
KeySizePx = FVector2D(12, 12);
|
|
SelectionSerial = Sequencer->GetViewModel()->GetSelection()->GetSerialNumber();
|
|
SelectionPreviewHash = Sequencer->GetSelectionPreview().GetSelectionHash();
|
|
bShowCurve = Channel && Channel->GetKeyArea()->ShouldShowCurve();
|
|
bShowKeyBars = Sequencer->GetSequencerSettings()->GetShowKeyBars();
|
|
bCollapseChildren = Outliner && Outliner->GetLinkedOutlinerItem() && Outliner->GetLinkedOutlinerItem()->IsExpanded() == false;
|
|
bIsChannelHovered = false;
|
|
if (!Hotspot || Hotspot->CastThis<FKeyHotspot>() || Hotspot->CastThis<FKeyBarHotspot>())
|
|
{
|
|
WeakHotspot = Hotspot;
|
|
RawHotspotDoNotUse = Hotspot.Get();
|
|
}
|
|
}
|
|
|
|
EViewDependentCacheFlags FChannelViewKeyCachedState::CompareTo(const FChannelViewKeyCachedState& Other) const
|
|
{
|
|
EViewDependentCacheFlags Flags = EViewDependentCacheFlags::None;
|
|
|
|
if (bIsChannelHovered || bIsChannelHovered != Other.bIsChannelHovered)
|
|
{
|
|
// Check if either hotspot expired
|
|
if ( (WeakHotspot.Pin() == nullptr && RawHotspotDoNotUse != nullptr) ||
|
|
(Other.WeakHotspot.Pin() == nullptr && Other.RawHotspotDoNotUse != nullptr) )
|
|
{
|
|
Flags |= EViewDependentCacheFlags::KeyStateChanged;
|
|
}
|
|
|
|
// Check if the hotspot has changed (this is necessary because theoretically both hotspots
|
|
// could still be _alive_ but Other.WeakHotspot is the active one)
|
|
if (WeakHotspot != Other.WeakHotspot)
|
|
{
|
|
// Hotspot has changed - maybe we (un)hovered some keys or a key bar
|
|
Flags |= EViewDependentCacheFlags::KeyStateChanged;
|
|
}
|
|
}
|
|
|
|
if (Other.NonLinearTransform.Pin() != NonLinearTransform.Pin())
|
|
{
|
|
Flags |= EViewDependentCacheFlags::ViewChanged;
|
|
}
|
|
|
|
if (ValidPlayRangeMin != Other.ValidPlayRangeMin || ValidPlayRangeMax != Other.ValidPlayRangeMax)
|
|
{
|
|
// The valid key ranges for the data has changed
|
|
Flags |= EViewDependentCacheFlags::KeyStateChanged;
|
|
}
|
|
|
|
if (SelectionSerial != Other.SelectionSerial || SelectionPreviewHash != Other.SelectionPreviewHash)
|
|
{
|
|
// Selection states have changed
|
|
Flags |= EViewDependentCacheFlags::KeyStateChanged;
|
|
}
|
|
|
|
if (VisibleRange != Other.VisibleRange)
|
|
{
|
|
Flags |= EViewDependentCacheFlags::ViewChanged;
|
|
|
|
const double RangeSize = VisibleRange.Size<FFrameTime>().AsDecimal();
|
|
const double OtherRangeSize = Other.VisibleRange.Size<FFrameTime>().AsDecimal();
|
|
|
|
if (!FMath::IsNearlyEqual(RangeSize, OtherRangeSize, RangeSize * 0.001))
|
|
{
|
|
Flags |= EViewDependentCacheFlags::ViewZoomed;
|
|
}
|
|
}
|
|
|
|
if (bShowCurve != Other.bShowCurve)
|
|
{
|
|
Flags |= EViewDependentCacheFlags::DataChanged;
|
|
}
|
|
|
|
if (bShowKeyBars != Other.bShowKeyBars)
|
|
{
|
|
Flags |= EViewDependentCacheFlags::DataChanged;
|
|
}
|
|
|
|
if (bCollapseChildren != Other.bCollapseChildren)
|
|
{
|
|
Flags |= EViewDependentCacheFlags::DataChanged;
|
|
}
|
|
|
|
if (KeySizePx != Other.KeySizePx)
|
|
{
|
|
Flags |= EViewDependentCacheFlags::ViewChanged | EViewDependentCacheFlags::ViewZoomed;
|
|
}
|
|
|
|
return Flags;
|
|
}
|
|
|
|
void SChannelView::Construct(const FArguments& InArgs, const FViewModelPtr& InViewModel, TSharedPtr<STrackAreaView> InTrackAreaView)
|
|
{
|
|
STrackAreaLaneView::Construct(
|
|
STrackAreaLaneView::FArguments()
|
|
[
|
|
InArgs._Content.Widget
|
|
]
|
|
, InViewModel, InTrackAreaView);
|
|
|
|
KeyBarColor = InArgs._KeyBarColor;
|
|
|
|
KeyRenderer.Initialize(InViewModel);
|
|
}
|
|
|
|
FReply SChannelView::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
|
{
|
|
TSharedPtr<STrackAreaView> TrackAreaView = WeakTrackAreaView.Pin();
|
|
TSharedPtr<FTrackAreaViewModel> TrackArea = TrackAreaView ? TrackAreaView->GetViewModel() : nullptr;
|
|
if (!TrackArea)
|
|
{
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
TSet<FSequencerSelectedKey> HoveredKeys;
|
|
|
|
// The hovered key is defined from the sequencer hotspot
|
|
if (TSharedPtr<FKeyHotspot> KeyHotspot = HotspotCast<FKeyHotspot>(TrackArea->GetHotspot()))
|
|
{
|
|
HoveredKeys = KeyHotspot->Keys;
|
|
}
|
|
|
|
// Create keys with middle mouse button only if there are no modifier keys down (to prevent zoom scrolling and creating keys at the same time)
|
|
if (!MouseEvent.GetModifierKeys().AnyModifiersDown() && MouseEvent.GetEffectingButton() == EKeys::MiddleMouseButton)
|
|
{
|
|
GEditor->BeginTransaction(LOCTEXT("CreateKey_Transaction", "Create Key"));
|
|
|
|
// Generate a key and set it as the PressedKey
|
|
TArray<FSequencerSelectedKey> NewKeys;
|
|
CreateKeysUnderMouse(MouseEvent.GetScreenSpacePosition(), MyGeometry, HoveredKeys, NewKeys);
|
|
|
|
if (NewKeys.Num())
|
|
{
|
|
TSharedPtr<FSequencer> Sequencer = LegacyGetSequencer();
|
|
FKeySelection& KeySelection = Sequencer->GetViewModel()->GetSelection()->KeySelection;
|
|
|
|
KeySelection.Empty();
|
|
for (const FSequencerSelectedKey& NewKey : NewKeys)
|
|
{
|
|
if (TSharedPtr<FChannelModel> Channel = NewKey.WeakChannel.Pin())
|
|
{
|
|
KeySelection.Select(Channel, NewKey.KeyHandle);
|
|
}
|
|
}
|
|
|
|
// Pass the event to the tool to copy the hovered key and move it
|
|
TrackArea->SetHotspot(MakeShared<FKeyHotspot>(MoveTemp(NewKeys), Sequencer));
|
|
|
|
// Return unhandled so that the EditTool can handle the mouse down based on the newly created keyframe and prepare to move it
|
|
return FReply::Unhandled();
|
|
}
|
|
}
|
|
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
FReply SChannelView::OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
|
{
|
|
TSharedPtr<STrackAreaView> TrackAreaView = WeakTrackAreaView.Pin();
|
|
TViewModelPtr<FSectionModel> Section = GetSection();
|
|
TSharedPtr<FTrackAreaViewModel> TrackArea = TrackAreaView ? TrackAreaView->GetViewModel() : nullptr;
|
|
if (!TrackArea || !Section)
|
|
{
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
TSharedPtr<FSequencer> Sequencer = LegacyGetSequencer();
|
|
|
|
|
|
// Don't fiddle with hotspots if there is an active drag
|
|
const ISequencerEditTool* EditTool = TrackArea->GetEditTool();
|
|
if (EditTool && EditTool->GetDragHotspot())
|
|
{
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
// Checked for hovered key
|
|
TSharedPtr<FKeyHotspot> KeyHotspot = HotspotCast<FKeyHotspot>(TrackArea->GetHotspot());
|
|
TSharedPtr<FKeyBarHotspot> KeyBarHotspot = HotspotCast<FKeyBarHotspot>(TrackArea->GetHotspot());
|
|
|
|
const FTimeToPixel RelativeTimeToPixel = GetRelativeTimeToPixel();
|
|
const FVector2D MousePixel = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
|
|
const float HalfHeightPx = MyGeometry.GetLocalSize().Y*.5f;
|
|
|
|
|
|
// Perform validation of existing hotspots, and hittesting of new ones if necessary.
|
|
// WARNING: This code will early-return once it has found a valid hotspot, so no
|
|
// code should be added after here if it must always run
|
|
if (KeyRendererCache.IsSet())
|
|
{
|
|
if (KeyHotspot)
|
|
{
|
|
const FFrameTime KeyTime = KeyHotspot->GetTime().Get(0);
|
|
const FVector2D HalfKeySizePx = KeyRendererCache->KeySizePx * .5f;
|
|
const float KeyPixel = RelativeTimeToPixel.FrameToPixel(KeyTime);
|
|
|
|
if (FMath::Abs(MousePixel.Y - HalfHeightPx) <= HalfKeySizePx.Y &&
|
|
FMath::Abs(MousePixel.X - KeyPixel) <= HalfKeySizePx.X)
|
|
{
|
|
// Key hotspot is still valid
|
|
return FReply::Handled();
|
|
}
|
|
}
|
|
else if (KeyBarHotspot)
|
|
{
|
|
if (FMath::Abs(MousePixel.Y - HalfHeightPx) <= 4.f &&
|
|
KeyBarHotspot->Range.Contains(RelativeTimeToPixel.PixelToFrame(MousePixel.X)))
|
|
{
|
|
// Keybar hotspot is still valid
|
|
return FReply::Handled();
|
|
}
|
|
}
|
|
}
|
|
|
|
TArray<FSequencerSelectedKey> KeysUnderMouse;
|
|
GetKeysUnderMouse(MouseEvent.GetScreenSpacePosition(), MyGeometry, KeysUnderMouse);
|
|
|
|
// If we are hovering over a new key, set that as the hotspot
|
|
if (KeysUnderMouse.Num())
|
|
{
|
|
TrackArea->SetHotspot(MakeShared<FKeyHotspot>(MoveTemp(KeysUnderMouse), Sequencer));
|
|
return FReply::Handled();
|
|
}
|
|
|
|
// Keys always render in the middle of the geometry so do a quick vertical test first
|
|
// We actually hittest a larger vertical area than the bars render to make it easier for user
|
|
// to hit key bars
|
|
if (FMath::Abs(MousePixel.Y - HalfHeightPx) <= 4.f)
|
|
{
|
|
FKeyRenderer::FKeyBar KeyBar;
|
|
if (KeyRenderer.HitTestKeyBar(RelativeTimeToPixel.PixelToFrame(MousePixel.X), KeyBar))
|
|
{
|
|
UMovieSceneSection* SectionObject = Section->GetSection();
|
|
TrackArea->SetHotspot(MakeShared<FKeyBarHotspot>(
|
|
KeyBar.Range,
|
|
KeyRendererResultToSelectedKeys(KeyBar.LeadingKeys, SectionObject),
|
|
KeyRendererResultToSelectedKeys(KeyBar.TrailingKeys, SectionObject),
|
|
Sequencer)
|
|
);
|
|
|
|
return FReply::Handled();
|
|
}
|
|
}
|
|
|
|
// Otherwise reset the hotspot if it is no longer valid
|
|
if (KeyHotspot || KeyBarHotspot)
|
|
{
|
|
TrackArea->SetHotspot(nullptr);
|
|
}
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
FReply SChannelView::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
|
{
|
|
if (!MouseEvent.GetModifierKeys().AnyModifiersDown() && MouseEvent.GetEffectingButton() == EKeys::MiddleMouseButton)
|
|
{
|
|
TSharedPtr<FSequencer> Sequencer = LegacyGetSequencer();
|
|
|
|
// Snap keys on mouse up since we want to create keys at the exact mouse position (ie. to keep the newly created keys under the mouse
|
|
// while dragging) but obey snapping rules if necessary
|
|
if (Sequencer->GetSequencerSettings()->GetForceWholeFrames())
|
|
{
|
|
Sequencer->SnapToFrame();
|
|
|
|
FSequencerSelection& Selection = *Sequencer->GetViewModel()->GetSelection();
|
|
for (FKeyHandle Key : Selection.KeySelection)
|
|
{
|
|
TSharedPtr<FChannelModel> Channel = Selection.KeySelection.GetModelForKey(Key);
|
|
if (Channel)
|
|
{
|
|
const FFrameNumber CurrentTime = Channel->GetKeyArea()->GetKeyTime(Key);
|
|
Sequencer->SetLocalTime(CurrentTime, ESnapTimeMode::STM_Interval);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
GEditor->EndTransaction();
|
|
|
|
// Return unhandled so that the EditTool can handle the mouse up based on the newly created keyframe and finish moving it
|
|
return FReply::Unhandled();
|
|
}
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
void SChannelView::OnMouseLeave(const FPointerEvent& MouseEvent)
|
|
{
|
|
SCompoundWidget::OnMouseLeave(MouseEvent);
|
|
|
|
TSharedPtr<STrackAreaView> TrackAreaView = WeakTrackAreaView.Pin();
|
|
TSharedPtr<FTrackAreaViewModel> TrackArea = TrackAreaView ? TrackAreaView->GetViewModel() : nullptr;
|
|
if (TrackArea)
|
|
{
|
|
TrackArea->SetHotspot(nullptr);
|
|
}
|
|
}
|
|
|
|
FReply SChannelView::OnMouseButtonDoubleClick(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
|
{
|
|
TSharedPtr<FSectionModel> Section = GetSection();
|
|
if (!Section)
|
|
{
|
|
return FReply::Handled();
|
|
}
|
|
|
|
if(MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton)
|
|
{
|
|
TArray<FSequencerSelectedKey> Keys;
|
|
GetKeysUnderMouse(MouseEvent.GetScreenSpacePosition(), MyGeometry, Keys);
|
|
TArray<FKeyHandle> KeyHandles;
|
|
for (FSequencerSelectedKey Key : Keys)
|
|
{
|
|
KeyHandles.Add(Key.KeyHandle);
|
|
}
|
|
if (KeyHandles.Num() > 0)
|
|
{
|
|
FReply Reply = Section->GetSectionInterface()->OnKeyDoubleClicked(KeyHandles);
|
|
return Reply.Handled();
|
|
}
|
|
}
|
|
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
int32 SChannelView::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
|
|
{
|
|
static const FName SelectionColorName("SelectionColor");
|
|
|
|
|
|
TSharedPtr<FSequencer> Sequencer = LegacyGetSequencer();
|
|
FViewModelPtr Model = WeakModel.Pin();
|
|
if (!Model)
|
|
{
|
|
return LayerId;
|
|
}
|
|
|
|
{
|
|
IMutableExtension* Mutable = Model->CastThis<IMutableExtension>();
|
|
bParentEnabled = bParentEnabled && (!Mutable || !Mutable->IsMuted());
|
|
}
|
|
|
|
LayerId = DrawLane(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled);
|
|
|
|
const bool bIncludeThis = true;
|
|
|
|
TSharedPtr<STrackAreaView> TrackAreaView = WeakTrackAreaView.Pin();
|
|
TSharedPtr<FTrackAreaViewModel> TrackArea = TrackAreaView ? TrackAreaView->GetViewModel() : nullptr;
|
|
TViewModelPtr<FSectionModel> Section = Model->FindAncestorOfType<FSectionModel>(bIncludeThis);
|
|
TViewModelPtr<FSequenceModel> Sequence = Model->FindAncestorOfType<FSequenceModel>();
|
|
|
|
UMovieSceneSection* SectionObject = Section ? Section->GetSection() : nullptr;
|
|
if (!Section || !Sequence || !SectionObject || !TrackArea)
|
|
{
|
|
return LayerId;
|
|
}
|
|
|
|
// Make the time <-> pixel converter relative to the section range rather than the view range
|
|
FTimeToPixel RelativeTimeToPixel = GetRelativeTimeToPixel();
|
|
|
|
FVector2D TopLeft = AllottedGeometry.AbsoluteToLocal(MyCullingRect.GetTopLeft());
|
|
FVector2D BottomRight = AllottedGeometry.AbsoluteToLocal(MyCullingRect.GetBottomRight());
|
|
|
|
TRange<FFrameTime> VisibleRange = TRange<FFrameTime>(
|
|
RelativeTimeToPixel.PixelToFrame(TopLeft.X),
|
|
RelativeTimeToPixel.PixelToFrame(BottomRight.X)
|
|
);
|
|
|
|
TViewModelPtr<FChannelModel> Channel = Model.ImplicitCast();
|
|
if (!Channel)
|
|
{
|
|
TOptional<FViewModelChildren> TopLevelChannels = Model->FindChildList(FTrackModel::GetTopLevelChannelType());
|
|
if (TopLevelChannels.IsSet())
|
|
{
|
|
Channel = TopLevelChannels->FindFirstChildOfType<FChannelModel>();
|
|
}
|
|
}
|
|
if (Channel)
|
|
{
|
|
FSequencerChannelPaintArgs ChannelPaintArgs = {
|
|
OutDrawElements,
|
|
Args,
|
|
AllottedGeometry,
|
|
MyCullingRect,
|
|
InWidgetStyle,
|
|
RelativeTimeToPixel,
|
|
bParentEnabled
|
|
};
|
|
LayerId = Channel->CustomPaint(ChannelPaintArgs, LayerId);
|
|
}
|
|
|
|
TSharedPtr<ISequencerSection> SectionInterface = Section->GetSectionInterface();
|
|
|
|
FSequencerKeyRendererInterface KeyRenderInterface(Sequencer.Get(), TrackArea->GetHotspot());
|
|
FChannelViewKeyCachedState NewCachedState(VisibleRange, TrackArea->GetHotspot(), Model, Sequencer.Get());
|
|
NewCachedState.bIsChannelHovered = IsHovered();
|
|
NewCachedState.KeySizePx = SectionInterface->GetKeySize();
|
|
NewCachedState.NonLinearTransform = RelativeTimeToPixel.NonLinearTransform;
|
|
|
|
FKeyBatchParameters Params(RelativeTimeToPixel);
|
|
Params.CacheState = KeyRendererCache.IsSet() ? NewCachedState.CompareTo(KeyRendererCache.GetValue()) : EViewDependentCacheFlags::All;
|
|
Params.VisibleRange = VisibleRange;
|
|
Params.ValidPlayRangeMin = NewCachedState.ValidPlayRangeMin;
|
|
Params.ValidPlayRangeMax = NewCachedState.ValidPlayRangeMax;
|
|
Params.bShowCurve = NewCachedState.bShowCurve;
|
|
Params.bShowKeyBars = NewCachedState.bShowKeyBars;
|
|
Params.bCollapseChildren = NewCachedState.bCollapseChildren;
|
|
Params.ClientInterface = &KeyRenderInterface;
|
|
|
|
FKeyRendererPaintArgs PaintArgs;
|
|
PaintArgs.DrawElements = &OutDrawElements;
|
|
PaintArgs.KeyThrobValue = SSequencerSection::GetKeySelectionThrobValue();
|
|
PaintArgs.DrawEffects = bParentEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect;
|
|
PaintArgs.SelectionColor = FAppStyle::GetSlateColor(SelectionColorName).GetColor(InWidgetStyle);
|
|
if (KeyBarColor.IsSet())
|
|
{
|
|
PaintArgs.KeyBarColor = KeyBarColor.Get();
|
|
}
|
|
|
|
if (Sequencer->GetSequencerSettings()->GetShowChannelColors() && Channel)
|
|
{
|
|
if (TOptional<FLinearColor> ChannelColor = Channel->GetKeyArea()->GetColor())
|
|
{
|
|
PaintArgs.CurveColor = ChannelColor.GetValue();
|
|
}
|
|
}
|
|
|
|
|
|
KeyRenderer.Update(Params, AllottedGeometry);
|
|
LayerId = KeyRenderer.DrawCurve(Params, AllottedGeometry, MyCullingRect, PaintArgs, LayerId);
|
|
LayerId = KeyRenderer.Draw(Params, AllottedGeometry, MyCullingRect, PaintArgs, LayerId + 10);
|
|
|
|
KeyRendererCache = NewCachedState;
|
|
|
|
return LayerId;
|
|
}
|
|
|
|
int32 SChannelView::DrawLane(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
|
|
{
|
|
FViewModelPtr Model = WeakModel.Pin();
|
|
TViewModelPtr<FSequenceModel> Sequence = Model ? Model->FindAncestorOfType<FSequenceModel>() : nullptr;
|
|
TViewModelPtr<FLinkedOutlinerExtension> Outliner = WeakModel.ImplicitPin();
|
|
if (!Sequence || !Outliner)
|
|
{
|
|
return LayerId;
|
|
}
|
|
|
|
TViewModelPtr<IOutlinerExtension> OutlinerItem = Outliner->GetLinkedOutlinerItem();
|
|
if (!OutlinerItem)
|
|
{
|
|
return LayerId;
|
|
}
|
|
|
|
FSequencer* Sequencer = Sequence->GetSequencerImpl().Get();
|
|
FSequencerSelection& Selection = Sequencer->GetSelection();
|
|
|
|
const ESlateDrawEffect DrawEffects = bParentEnabled
|
|
? ESlateDrawEffect::None
|
|
: ESlateDrawEffect::DisabledEffect;
|
|
|
|
FLinearColor HighlightColor;
|
|
bool bDrawHighlight = false;
|
|
if (Sequencer->GetSelection().NodeHasSelectedKeysOrSections(OutlinerItem))
|
|
{
|
|
bDrawHighlight = true;
|
|
HighlightColor = FLinearColor(1.0f, 1.0f, 1.0f, 0.15f);
|
|
}
|
|
else if (TViewModelPtr<IHoveredExtension> Hovered = OutlinerItem.ImplicitCast())
|
|
{
|
|
if (Hovered->IsHovered())
|
|
{
|
|
bDrawHighlight = true;
|
|
HighlightColor = FLinearColor(1.0f, 1.0f, 1.0f, 0.05f);
|
|
}
|
|
}
|
|
|
|
// --------------------------------------------
|
|
// Draw hover or selection highlight
|
|
if (bDrawHighlight)
|
|
{
|
|
static const FName HighlightBrushName("Sequencer.AnimationOutliner.DefaultBorder");
|
|
|
|
FSlateDrawElement::MakeBox(
|
|
OutDrawElements,
|
|
LayerId++,
|
|
AllottedGeometry.ToPaintGeometry(),
|
|
FAppStyle::GetBrush(HighlightBrushName),
|
|
DrawEffects,
|
|
HighlightColor
|
|
);
|
|
}
|
|
|
|
// --------------------------------------------
|
|
// Draw outliner selection tint
|
|
if (Selection.Outliner.IsSelected(OutlinerItem))
|
|
{
|
|
static const FName SelectionColorName("SelectionColor");
|
|
static const FName HighlightBrushName("Sequencer.AnimationOutliner.DefaultBorder");
|
|
|
|
FSlateDrawElement::MakeBox(
|
|
OutDrawElements,
|
|
LayerId++,
|
|
AllottedGeometry.ToPaintGeometry(),
|
|
FAppStyle::GetBrush(HighlightBrushName),
|
|
DrawEffects,
|
|
FAppStyle::GetSlateColor(SelectionColorName).GetColor(InWidgetStyle).CopyWithNewOpacity(0.2f)
|
|
);
|
|
}
|
|
|
|
return LayerId;
|
|
}
|
|
|
|
TViewModelPtr<FSectionModel> SChannelView::GetSection() const
|
|
{
|
|
FViewModelPtr Model = WeakModel.Pin();
|
|
if (Model)
|
|
{
|
|
constexpr bool bIncludeThis = true;
|
|
return Model->FindAncestorOfType<FSectionModel>(bIncludeThis);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
TSharedPtr<FSequencer> SChannelView::LegacyGetSequencer() const
|
|
{
|
|
FViewModelPtr Model = WeakModel.Pin();
|
|
if (Model)
|
|
{
|
|
TViewModelPtr<FSequenceModel> Sequence = Model->FindAncestorOfType<FSequenceModel>();
|
|
if (Sequence)
|
|
{
|
|
return Sequence->GetSequencerImpl();
|
|
}
|
|
}
|
|
ensureMsgf(false, TEXT("No valid Sequencer exists"));
|
|
return nullptr;
|
|
}
|
|
|
|
void SChannelView::GetKeysUnderMouse(const FVector2D& MousePosition, const FGeometry& AllottedGeometry, TArray<FSequencerSelectedKey>& OutKeys) const
|
|
{
|
|
TViewModelPtr<FSectionModel> Section = GetSection();
|
|
if (!Section || !KeyRendererCache.IsSet())
|
|
{
|
|
return;
|
|
}
|
|
|
|
const float HalfHeightPx = AllottedGeometry.GetLocalSize().Y*.5f;
|
|
const FVector2D MousePixel = AllottedGeometry.AbsoluteToLocal(MousePosition);
|
|
|
|
// Keys always render in the middle of the geometry so do a quick vertical test first
|
|
if (FMath::Abs(MousePixel.Y - HalfHeightPx) <= KeyRendererCache->KeySizePx.Y*.5f)
|
|
{
|
|
GetKeysAtPixelX(MousePixel.X, OutKeys);
|
|
}
|
|
}
|
|
|
|
void SChannelView::GetKeysAtPixelX(float LocalMousePixelX, TArray<FSequencerSelectedKey>& OutKeys) const
|
|
{
|
|
TViewModelPtr<FSectionModel> Section = GetSection();
|
|
UMovieSceneSection* SectionObject = Section ? Section->GetSection() : nullptr;
|
|
|
|
if (!SectionObject)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Make the time <-> pixel converter relative to the section range rather than the view range
|
|
FTimeToPixel RelativeTimeToPixel = GetRelativeTimeToPixel();
|
|
|
|
TArray<FKeyRenderer::FKeysForModel> Results;
|
|
KeyRenderer.HitTestKeys(RelativeTimeToPixel.PixelToFrame(LocalMousePixelX), Results);
|
|
OutKeys = KeyRendererResultToSelectedKeys(Results, SectionObject);
|
|
}
|
|
|
|
void SChannelView::CreateKeysUnderMouse(const FVector2D& MousePosition, const FGeometry& AllottedGeometry, const TSet<FSequencerSelectedKey>& InPressedKeys, TArray<FSequencerSelectedKey>& OutKeys)
|
|
{
|
|
FViewModelPtr Model = WeakModel.Pin();
|
|
TViewModelPtr<FSectionModel> Section = GetSection();
|
|
if (!Section || !Model)
|
|
{
|
|
return;
|
|
}
|
|
|
|
UMovieSceneSection* SectionObject = Section->GetSection();
|
|
if (!SectionObject)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (SectionObject->IsReadOnly())
|
|
{
|
|
return;
|
|
}
|
|
|
|
FTimeToPixel RelativeTimeToPixel = GetRelativeTimeToPixel();
|
|
|
|
// If the pressed key exists, offset the new key and look for it in the newly laid out key areas
|
|
if (InPressedKeys.Num())
|
|
{
|
|
SectionObject->Modify();
|
|
|
|
// Offset by 1 pixel worth of time if possible
|
|
const FFrameTime TimeFuzz = RelativeTimeToPixel.PixelDeltaToFrame(1.f);
|
|
|
|
for (const FSequencerSelectedKey& PressedKey : InPressedKeys)
|
|
{
|
|
const FFrameNumber CurrentTime = PressedKey.WeakChannel.Pin()->GetKeyArea()->GetKeyTime(PressedKey.KeyHandle);
|
|
const FKeyHandle NewHandle = PressedKey.WeakChannel.Pin()->GetKeyArea()->DuplicateKey(PressedKey.KeyHandle);
|
|
|
|
PressedKey.WeakChannel.Pin()->GetKeyArea()->SetKeyTime(NewHandle, CurrentTime + TimeFuzz.FrameNumber);
|
|
OutKeys.Add(FSequencerSelectedKey(*SectionObject, PressedKey.WeakChannel, NewHandle));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TSharedPtr<FTrackModel> TrackModel = Section->FindAncestorOfType<FTrackModel>();
|
|
TSharedPtr<IObjectBindingExtension> ObjectBindingExtension = Section->FindAncestorOfType<IObjectBindingExtension>();
|
|
|
|
FVector2D LocalSpaceMousePosition = AllottedGeometry.AbsoluteToLocal(MousePosition);
|
|
const FFrameTime CurrentTime = RelativeTimeToPixel.PixelToFrame(LocalSpaceMousePosition.X);
|
|
|
|
TArray<TSharedRef<IKeyArea>> ValidKeyAreasUnderCursor;
|
|
for (TViewModelPtr<FChannelModel> Channel : Model->GetDescendantsOfType<FChannelModel>(true))
|
|
{
|
|
ValidKeyAreasUnderCursor.Add(Channel->GetKeyArea().ToSharedRef());
|
|
}
|
|
|
|
FScopedTransaction Transaction(LOCTEXT("CreateKeysUnderMouse", "Create keys under mouse"));
|
|
TArray<FAddKeyResult> Results;
|
|
FAddKeyOperation::FromKeyAreas(TrackModel->GetTrackEditor().Get(), ValidKeyAreasUnderCursor).Commit(CurrentTime.FrameNumber, *LegacyGetSequencer(), &Results);
|
|
|
|
for (FAddKeyResult& Result : Results)
|
|
{
|
|
for (TViewModelPtr<FChannelModel> Channel : Model->GetDescendantsOfType<FChannelModel>(true))
|
|
{
|
|
if (Channel->GetChannel() == Result.KeyArea->GetChannel().Get())
|
|
{
|
|
OutKeys.Add(FSequencerSelectedKey(*SectionObject, Channel, Result.KeyHandle));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace UE::Sequencer
|
|
|
|
#undef LOCTEXT_NAMESPACE
|
|
|