Files
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

1043 lines
30 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MVVM/ViewModels/TrackModel.h"
#include "MVVM/SharedViewModelData.h"
#include "MVVM/Extensions/IObjectBindingExtension.h"
#include "MVVM/Extensions/IRecyclableExtension.h"
#include "MVVM/Extensions/ITrackExtension.h"
#include "MVVM/ViewModels/ChannelModel.h"
#include "MVVM/ViewModels/FolderModel.h"
#include "MVVM/ViewModels/ViewModelIterators.h"
#include "MVVM/ViewModels/SectionModel.h"
#include "MVVM/ViewModels/SequenceModel.h"
#include "MVVM/ViewModels/TrackModelLayoutBuilder.h"
#include "MVVM/ViewModels/TrackRowModel.h"
#include "MVVM/SectionModelStorageExtension.h"
#include "MVVM/TrackRowModelStorageExtension.h"
#include "MVVM/Views/SOutlinerTrackView.h"
#include "MVVM/ViewModels/SequencerEditorViewModel.h"
#include "MVVM/Selection/Selection.h"
#include "MovieScene.h"
#include "MovieSceneFolder.h"
#include "MovieSceneTrack.h"
#include "MovieSceneSection.h"
#include "MovieSceneNameableTrack.h"
#include "Decorations/MovieSceneMuteSoloDecoration.h"
#include "Tracks/MovieScene3DTransformTrack.h"
#include "Tracks/MovieScene3DTransformTrack.h"
#include "Tracks/MovieScenePrimitiveMaterialTrack.h"
#include "EntitySystem/IMovieSceneBlenderSystemSupport.h"
#include "ISequencer.h"
#include "ISequencerSection.h"
#include "ISequencerTrackEditor.h"
#include "SSequencer.h"
#include "SequencerNodeTree.h"
#include "SequencerUtilities.h"
#include "SequencerCommonHelpers.h"
#include "ScopedTransaction.h"
#include "Styling/AppStyle.h"
#define LOCTEXT_NAMESPACE "TrackModel"
namespace UE::Sequencer
{
FTrackModel::FTrackModel(UMovieSceneTrack* Track)
: SectionList(EViewModelListType::TrackArea)
, TopLevelChannelList(GetTopLevelChannelGroupType())
, WeakTrack(Track)
, bNeedsUpdate(false)
{
RegisterChildList(&SectionList);
RegisterChildList(&TopLevelChannelList);
SetIdentifier(Track->GetFName());
}
FTrackModel::~FTrackModel()
{
}
EViewModelListType FTrackModel::GetTopLevelChannelType()
{
static EViewModelListType TopLevelChannel = RegisterCustomModelListType();
return TopLevelChannel;
}
EViewModelListType FTrackModel::GetTopLevelChannelGroupType()
{
static EViewModelListType TopLevelChannelGroup = RegisterCustomModelListType();
return TopLevelChannelGroup;
}
FViewModelChildren FTrackModel::GetSectionModels()
{
return GetChildrenForList(&SectionList);
}
FViewModelChildren FTrackModel::GetTopLevelChannels()
{
return GetChildrenForList(&TopLevelChannelList);
}
UMovieSceneTrack* FTrackModel::GetTrack() const
{
return WeakTrack.Get();
}
int32 FTrackModel::GetRowIndex() const
{
return 0;
}
TSharedPtr<ISequencerTrackEditor> FTrackModel::GetTrackEditor() const
{
return TrackEditor;
}
void FTrackModel::OnConstruct()
{
UMovieSceneTrack* Track = GetTrack();
TSharedPtr<FSequenceModel> SequenceModel = FindAncestorOfType<FSequenceModel>();
check(SequenceModel && Track);
if (!IsLinked())
{
Track->EventHandlers.Link(this);
}
TrackEditor = SequenceModel->GetSequencer()->GetTrackEditor(Track);
ForceUpdate();
}
void FTrackModel::OnModifiedDirectly(UMovieSceneSignedObject*)
{
if (!bNeedsUpdate)
{
bNeedsUpdate = true;
UMovieSceneSignedObject::AddFlushSignal(SharedThis(this));
}
}
void FTrackModel::OnModifiedIndirectly(UMovieSceneSignedObject* InSource)
{
if (!bNeedsUpdate)
{
bNeedsUpdate = true;
UMovieSceneSignedObject::AddFlushSignal(SharedThis(this));
}
}
void FTrackModel::OnDeferredModifyFlush()
{
if (bNeedsUpdate)
{
ForceUpdate();
bNeedsUpdate = false;
}
}
void FTrackModel::ForceUpdate()
{
FViewModelHierarchyOperation HierarchyOperation(GetSharedData());
FViewModelChildren OutlinerChildren = GetChildList(EViewModelListType::Outliner);
FViewModelChildren SectionChildren = GetChildList(EViewModelListType::TrackArea);
FViewModelChildren TopLevelChannelChildren = GetChildList(GetTopLevelChannelGroupType());
UMovieSceneTrack* Track = WeakTrack.Get();
if (!Track)
{
// Free outliner and section children, this track is gone.
OutlinerChildren.Empty();
SectionChildren.Empty();
TopLevelChannelChildren.Empty();
return;
}
TSharedPtr<FSequenceModel> SequenceModel = FindAncestorOfType<FSequenceModel>();
if (!SequenceModel)
{
// Not part of a full sequence hierarchy yet - wait for OnSetSharedData()
return;
}
FSectionModelStorageExtension* SectionModelStorage = SequenceModel->CastDynamic<FSectionModelStorageExtension>();
FGuid ObjectBinding;
if (TSharedPtr<IObjectBindingExtension> ObjectBindingExtension = FindAncestorOfType<IObjectBindingExtension>())
{
ObjectBinding = ObjectBindingExtension->GetObjectGuid();
}
TBitArray<> PopulatedRows;
for (UMovieSceneSection* Section : Track->GetAllSections())
{
const int32 RowIndex = Section->GetRowIndex();
PopulatedRows.PadToNum(RowIndex + 1, false);
PopulatedRows[RowIndex] = true;
}
const int32 NumRows = PopulatedRows.CountSetBits();
if (NumRows == 0)
{
// Reset expansion state if this track can no longer be expanded
SetExpansion(false);
// Clear any left-over row models, layout models, or section models.
OutlinerChildren.Empty();
SectionChildren.Empty();
TopLevelChannelChildren.Empty();
}
else if (NumRows == 1)
{
// Keep sections alive by retaining the previous list temporarily
TSharedPtr<FViewModel> SectionsTail;
FScopedViewModelListHead RecycledModels(AsShared(), EViewModelListType::Recycled);
GetChildrenForList(&SectionList).MoveChildrenTo<IRecyclableExtension>(RecycledModels.GetChildren(), IRecyclableExtension::CallOnRecycle);
bool bNeedsLayout = NumRows != PreviousLayoutNumRows;
//Sections that weren't expanded
TSet<TSharedPtr<FSectionModel>> CollapsedSections;
// Add all sections directly to this track row
for (UMovieSceneSection* Section : Track->GetAllSections())
{
TSharedPtr<FSectionModel> SectionModel = SectionModelStorage->FindModelForSection(Section);
if (!SectionModel && TrackEditor)
{
TSharedRef<ISequencerSection> SectionInterface = TrackEditor->MakeSectionInterface(*Section, *Track, ObjectBinding);
SectionModel = SectionModelStorage->CreateModelForSection(Section, SectionInterface);
bNeedsLayout = true;
}
else
{
TViewModelPtr<IOutlinerExtension> Outliner = SectionModel->FindAncestorOfType<IOutlinerExtension>();
if (Outliner && (Outliner->IsExpanded() == false))
{
CollapsedSections.Add(SectionModel);
}
}
if (ensure(SectionModel))
{
bNeedsLayout |= SectionModel->NeedsLayout();
// Move the child back into the real section list
SectionChildren.InsertChild(SectionModel, SectionsTail);
SectionsTail = SectionModel;
}
}
// If we are discarding any sections (because they still remain in the recycled list) we must run the layout
bNeedsLayout |= !RecycledModels.GetChildren().IsEmpty();
if (bNeedsLayout)
{
OutlinerChildren.MoveChildrenTo<IRecyclableExtension>(RecycledModels.GetChildren(), IRecyclableExtension::CallOnRecycle);
TopLevelChannelChildren.MoveChildrenTo<IRecyclableExtension>(RecycledModels.GetChildren(), IRecyclableExtension::CallOnRecycle);
// Rebuild the outliner layout for this track. This will clear our children and rebuild them if needed
// (with potentially recycled children), so if we went from, say, 2 rows to 1 row, it should correctly
// discard any children we don't need anymore.
FTrackModelLayoutBuilder LayoutBuilder(AsShared());
for (TSharedPtr<FSectionModel> Section : TViewModelListIterator<FSectionModel>(&SectionList))
{
LayoutBuilder.RefreshLayout(Section);
if (CollapsedSections.Contains(Section))
{
TViewModelPtr<IOutlinerExtension> Outliner = Section->FindAncestorOfType<IOutlinerExtension>();
if (Outliner)
{
Outliner->SetExpansion(false);
}
}
}
if (OutlinerChildren.IsEmpty())
{
// Reset expansion state if this track can no longer be expanded
SetExpansion(false);
}
}
}
else
{
// Always expand parent tracks
SetExpansion(true);
// Keep sections alive by retaining the previous list temporarily
// This should only be required if this track previously represented
// a single row, but now there are multiple rows
FScopedViewModelListHead RecycledModels(AsShared(), EViewModelListType::Recycled);
GetChildrenForList(&SectionList).MoveChildrenTo<IRecyclableExtension>(RecycledModels.GetChildren(), IRecyclableExtension::CallOnRecycle);
OutlinerChildren.MoveChildrenTo<IRecyclableExtension>(RecycledModels.GetChildren(), IRecyclableExtension::CallOnRecycle);
TopLevelChannelChildren.MoveChildrenTo<IRecyclableExtension>(RecycledModels.GetChildren(), IRecyclableExtension::CallOnRecycle);
// We need to build row models so let's grab the storage for that
FTrackRowModelStorageExtension* TrackRowModelStorage = SequenceModel->CastDynamic<FTrackRowModelStorageExtension>();
check(TrackRowModelStorage);
// We will build some info about what sections go on what row
// Note: the OldSections pointer is just to keep the row section models alive until we re-assign them
struct FRowData
{
TSharedPtr<FTrackRowModel> Row;
TSharedPtr<FViewModel> SectionsTail;
TUniquePtr<FScopedViewModelListHead> RecycledModels;
bool bNeedsLayout = false;
};
TArray<FRowData, TInlineAllocator<8>> RowModels;
RowModels.SetNum(PopulatedRows.Num());
// Create track row models for all populated rows
TSharedPtr<FTrackRowModel> LastTrackRowModel;
for (TConstSetBitIterator<> It(PopulatedRows); It; ++It)
{
const int32 RowIndex = It.GetIndex();
RowModels[RowIndex].bNeedsLayout = NumRows != PreviousLayoutNumRows;
TSharedPtr<FTrackRowModel> TrackRowModel = TrackRowModelStorage->FindModelForTrackRow(Track, RowIndex);
if (!TrackRowModel)
{
TrackRowModel = TrackRowModelStorage->CreateModelForTrackRow(Track, RowIndex);
RowModels[RowIndex].bNeedsLayout = true;
}
if (ensure(TrackRowModel))
{
OutlinerChildren.InsertChild(TrackRowModel, LastTrackRowModel);
LastTrackRowModel = TrackRowModel;
RowModels[RowIndex].Row = TrackRowModel;
// Recycle sections, outliner children, and more, while keeping them alive.
RowModels[RowIndex].RecycledModels = MakeUnique<FScopedViewModelListHead>(TrackRowModel, EViewModelListType::Recycled);
FViewModelChildren RecycledRowModels = RowModels[RowIndex].RecycledModels->GetChildren();
TrackRowModel->GetSectionModels().MoveChildrenTo<IRecyclableExtension>(RecycledRowModels, IRecyclableExtension::CallOnRecycle);
}
}
// Add all sections to both their appropriate track rows and ourselves
for (UMovieSceneSection* Section : Track->GetAllSections())
{
const int32 RowIndex = Section->GetRowIndex();
TSharedPtr<FSectionModel> SectionModel = SectionModelStorage->FindModelForSection(Section);
if (!SectionModel && TrackEditor)
{
TSharedRef<ISequencerSection> SectionInterface = TrackEditor->MakeSectionInterface(*Section, *Track, ObjectBinding);
SectionModel = SectionModelStorage->CreateModelForSection(Section, SectionInterface);
RowModels[RowIndex].bNeedsLayout = true;
}
else if (SectionModel)
{
RowModels[RowIndex].bNeedsLayout |= SectionModel->NeedsLayout();
}
RowModels[RowIndex].Row->GetSectionModels().InsertChild(SectionModel, RowModels[RowIndex].SectionsTail);
RowModels[RowIndex].SectionsTail = SectionModel;
}
// Rebuild the outliner layout for each track row
for (FRowData& RowData : RowModels)
{
const bool bStillHasRecycledChildren = RowData.RecycledModels && !RowData.RecycledModels->GetChildren().IsEmpty();
if (RowData.Row && (RowData.bNeedsLayout || bStillHasRecycledChildren))
{
FViewModelChildren RecycledRowModels = RowData.RecycledModels->GetChildren();
RowData.Row->GetChildList(EViewModelListType::Outliner).MoveChildrenTo<IRecyclableExtension>(RecycledRowModels, IRecyclableExtension::CallOnRecycle);
RowData.Row->GetTopLevelChannels().MoveChildrenTo<IRecyclableExtension>(RecycledRowModels, IRecyclableExtension::CallOnRecycle);
FTrackModelLayoutBuilder LayoutBuilder(RowData.Row->AsShared());
for (TSharedPtr<FSectionModel> Section : RowData.Row->GetChildrenOfType<FSectionModel>(EViewModelListType::TrackArea))
{
LayoutBuilder.RefreshLayout(Section);
}
}
// else: unset row... it should only happen while we are dragging sections, until
// we fixup row indices
}
}
PreviousLayoutNumRows = NumRows;
}
FOutlinerSizing FTrackModel::GetOutlinerSizing() const
{
FViewDensityInfo Density = GetEditor()->GetViewDensity();
float Height = Density.UniformHeight.Get(SequencerLayoutConstants::SectionAreaDefaultHeight);
for (TSharedPtr<FSectionModel> Section : SectionList.Iterate<FSectionModel>())
{
Height = Section->GetSectionInterface()->GetSectionHeight(Density);
break;
}
return FOutlinerSizing(Height);
}
void FTrackModel::GetIdentifierForGrouping(TStringBuilder<128>& OutString) const
{
FOutlinerItemModel::GetIdentifier().ToString(OutString);
}
FTrackAreaParameters FTrackModel::GetTrackAreaParameters() const
{
FTrackAreaParameters Params;
Params.LaneType = ETrackAreaLaneType::Nested;
Params.TrackLanePadding.Bottom = 1.f;
return Params;
}
FViewModelVariantIterator FTrackModel::GetTrackAreaModelList() const
{
return &SectionList;
}
FViewModelVariantIterator FTrackModel::GetTopLevelChildTrackAreaModels() const
{
return &TopLevelChannelList;
}
bool FTrackModel::CanRename() const
{
UMovieSceneNameableTrack* NameableTrack = Cast<UMovieSceneNameableTrack>(GetTrack());
return NameableTrack && NameableTrack->CanRename();
}
void FTrackModel::Rename(const FText& NewName)
{
UMovieSceneNameableTrack* NameableTrack = ::Cast<UMovieSceneNameableTrack>(GetTrack());
if (NameableTrack && !NameableTrack->GetDisplayName().EqualTo(NewName))
{
const FScopedTransaction Transaction(NSLOCTEXT("SequencerTrackNode", "RenameTrack", "Rename Track"));
NameableTrack->SetDisplayName(NewName);
SetIdentifier(FName(*NewName.ToString()));
// HACK: this should not exist but is required to make renaming emitters work in niagara
if (TSharedPtr<FSequenceModel> OwnerModel = FindAncestorOfType<FSequenceModel>())
{
OwnerModel->GetSequencer()->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::TrackValueChanged);
}
}
}
bool FTrackModel::IsRenameValidImpl(const FText& NewName, FText& OutErrorMessage) const
{
UMovieSceneNameableTrack* NameableTrack = ::Cast<UMovieSceneNameableTrack>(GetTrack());
if (NameableTrack)
{
return NameableTrack->ValidateDisplayName(NewName, OutErrorMessage);
}
return false;
}
void FTrackModel::SortChildren()
{
// Nothing to do
}
FSortingKey FTrackModel::GetSortingKey() const
{
FSortingKey SortingKey;
if (UMovieSceneTrack* Track = GetTrack())
{
SortingKey.DisplayName = Track->GetDisplayName();
SortingKey.CustomOrder = Track->GetSortingOrder();
}
// When inside object bindings, we come after other object bindings. Elsewhere, we come before object bindings.
const bool bHasParentObjectBinding = (CastParent<IObjectBindingExtension>() != nullptr);
SortingKey.PrioritizeBy(bHasParentObjectBinding ? 1 : 2);
return SortingKey;
}
void FTrackModel::SetCustomOrder(int32 InCustomOrder)
{
if (UMovieSceneTrack* Track = GetTrack())
{
Track->SetSortingOrder(InCustomOrder);
}
}
bool FTrackModel::HasCurves() const
{
FViewModelChildren TopLevelChannels = const_cast<FTrackModel*>(this)->GetTopLevelChannels();
for (auto It(TopLevelChannels.IterateSubList<FChannelGroupModel>()); It; ++It)
{
if (It->HasCurves())
{
return true;
}
}
return false;
}
void FTrackModel::CreateCurveModels(TArray<TUniquePtr<FCurveModel>>& OutCurveModels)
{
TViewModelPtr<FChannelGroupModel> ChannelGroup = TopLevelChannelList.GetHead().ImplicitCast();
if (ChannelGroup)
{
ChannelGroup->CreateCurveModels(OutCurveModels);
}
}
bool FTrackModel::GetDefaultExpansionState() const
{
TViewModelListIterator<ITrackExtension> It = GetChildrenOfType<ITrackExtension>();
const bool bHasTrackRows = (bool)It;
if (bHasTrackRows)
{
return true;
}
UMovieSceneTrack* Track = GetTrack();
if (TrackEditor && Track)
{
return TrackEditor->GetDefaultExpansionState(Track);
}
return false;
}
bool FTrackModel::IsDimmed() const
{
UMovieSceneTrack* Track = GetTrack();
if (Track)
{
if (Track->IsEvalDisabled())
{
return true;
}
if (Track->ConditionContainer.Condition)
{
FGuid BindingID;
FMovieSceneSequenceID SequenceID = MovieSceneSequenceID::Root;
if (TViewModelPtr<FObjectBindingModel> ObjectBindingModel = FindAncestorOfType<FObjectBindingModel>())
{
BindingID = ObjectBindingModel->GetObjectGuid();
}
if (TViewModelPtr<FSequenceModel> SequenceModel = FindAncestorOfType<FSequenceModel>())
{
SequenceID = SequenceModel->GetSequenceID();
if (TSharedPtr<FSequencerEditorViewModel> SequencerModel = SequenceModel->GetEditor())
{
if (!MovieSceneHelpers::EvaluateSequenceCondition(BindingID, SequenceID, Track->ConditionContainer.Condition, Track, SequencerModel->GetSequencer()->GetSharedPlaybackState()))
{
return true;
}
}
}
}
}
return FOutlinerItemModel::IsDimmed();
}
FSlateFontInfo FTrackModel::GetLabelFont() const
{
bool bAllAnimated = false;
TViewModelPtr<FChannelGroupModel> TopLevelChannel = TopLevelChannelList.GetHead().ImplicitCast();
if (TopLevelChannel)
{
for (const TViewModelPtr<FChannelModel>& ChannelModel : TopLevelChannel->GetTrackAreaModelListAs<FChannelModel>())
{
FMovieSceneChannel* Channel = ChannelModel->GetChannel();
if (!Channel || Channel->GetNumKeys() == 0)
{
return FOutlinerItemModel::GetLabelFont();
}
else
{
bAllAnimated = true;
}
}
if (bAllAnimated == true)
{
return FAppStyle::GetFontStyle("Sequencer.AnimationOutliner.ItalicFont");
}
}
return FOutlinerItemModel::GetLabelFont();
}
const FSlateBrush* FTrackModel::GetIconBrush() const
{
return TrackEditor ? TrackEditor->GetIconBrush() : nullptr;
}
FText FTrackModel::GetLabel() const
{
UMovieSceneTrack* Track = GetTrack();
return Track ? Track->GetDisplayName() : FText::GetEmpty();
}
FSlateColor FTrackModel::GetLabelColor() const
{
UMovieSceneTrack* Track = GetTrack();
if (!Track)
{
return FSlateColor::UseForeground();
}
FMovieSceneLabelParams LabelParams;
LabelParams.bIsDimmed = IsDimmed();
if (TViewModelPtr<FSequenceModel> SequenceModel = FindAncestorOfType<FSequenceModel>())
{
if (TSharedPtr<FSequencerEditorViewModel> SequencerModel = SequenceModel->GetEditor())
{
LabelParams.SequenceID = SequenceModel->GetSequenceID();
LabelParams.Player = SequencerModel->GetSequencer().Get();
if (LabelParams.Player)
{
if (TViewModelPtr<FObjectBindingModel> ObjectBindingModel = FindAncestorOfType<FObjectBindingModel>())
{
LabelParams.BindingID = ObjectBindingModel->GetObjectGuid();
// If the object binding model has an invalid binding, we want to use its label color, as it may be red or gray depending on situation
// and we want the children of that to have the same color.
// Otherwise, we can use the track's label color below
TArrayView<TWeakObjectPtr<> > BoundObjects = LabelParams.Player->FindBoundObjects(LabelParams.BindingID, LabelParams.SequenceID);
if (BoundObjects.Num() == 0)
{
return ObjectBindingModel->GetLabelColor();
}
}
}
}
}
return Track->GetLabelColor(LabelParams);
}
FText FTrackModel::GetLabelToolTipText() const
{
UMovieSceneTrack* Track = GetTrack();
if (!Track)
{
return FText();
}
FMovieSceneLabelParams LabelParams;
LabelParams.bIsDimmed = IsDimmed();
if (TViewModelPtr<FSequenceModel> SequenceModel = FindAncestorOfType<FSequenceModel>())
{
if (TSharedPtr<FSequencerEditorViewModel> SequencerModel = SequenceModel->GetEditor())
{
LabelParams.SequenceID = SequenceModel->GetSequenceID();
LabelParams.Player = SequencerModel->GetSequencer().Get();
if (LabelParams.Player)
{
if (TViewModelPtr<FObjectBindingModel> ObjectBindingModel = FindAncestorOfType<FObjectBindingModel>())
{
LabelParams.BindingID = ObjectBindingModel->GetObjectGuid();
}
return Track->GetDisplayNameToolTipText(LabelParams);
}
}
}
return FText();
}
TSharedPtr<SWidget> FTrackModel::CreateOutlinerViewForColumn(const FCreateOutlinerViewParams& InParams, const FName& InColumnName)
{
FBuildColumnWidgetParams Params(SharedThis(this), InParams);
return TrackEditor->BuildOutlinerColumnWidget(Params, InColumnName);
}
bool FTrackModel::IsResizable() const
{
UMovieSceneTrack* Track = GetTrack();
return Track && TrackEditor->IsResizable(Track);
}
void FTrackModel::Resize(float NewSize)
{
UMovieSceneTrack* Track = GetTrack();
if (Track && TrackEditor->IsResizable(Track))
{
TrackEditor->Resize(NewSize, Track);
}
}
ELockableLockState FTrackModel::GetLockState() const
{
int32 NumSections = 0;
int32 NumLockedSections = 0;
for (const TViewModelPtr<FSectionModel>& Section : SectionList.Iterate<FSectionModel>())
{
++NumSections;
UMovieSceneSection* SectionObject = Section->GetSection();
if (SectionObject && SectionObject->IsLocked())
{
++NumLockedSections;
}
}
if (NumSections == 0 || NumLockedSections == 0)
{
return ELockableLockState::None;
}
return NumLockedSections == NumSections ? ELockableLockState::Locked : ELockableLockState::PartiallyLocked;
}
void FTrackModel::SetIsLocked(bool bInIsLocked)
{
for (const TViewModelPtr<FSectionModel>& Section : SectionList.Iterate<FSectionModel>())
{
UMovieSceneSection* SectionObject = Section->GetSection();
if (SectionObject)
{
SectionObject->Modify();
SectionObject->SetIsLocked(bInIsLocked);
}
}
}
const UMovieSceneCondition* FTrackModel::GetCondition() const
{
UMovieSceneTrack* const Track = GetTrack();
if (IsValid(Track))
{
return Track->ConditionContainer.Condition;
}
return nullptr;
}
EConditionableConditionState FTrackModel::GetConditionState() const
{
TSharedPtr<FSequenceModel> SequenceModel = FindAncestorOfType<FSequenceModel>();
TSharedPtr<ISequencer> Sequencer = SequenceModel ? SequenceModel->GetSequencer() : nullptr;
if (Sequencer)
{
FGuid BindingID;
if (TSharedPtr<IObjectBindingExtension> ParentBinding = FindAncestorOfType<IObjectBindingExtension>())
{
BindingID = ParentBinding->GetObjectGuid();
}
UMovieSceneTrack* const Track = GetTrack();
if (IsValid(Track))
{
if (Track->ConditionContainer.Condition)
{
if (Track->ConditionContainer.Condition->bEditorForceTrue)
{
return EConditionableConditionState::HasConditionEditorForceTrue;
}
if (MovieSceneHelpers::EvaluateSequenceCondition(BindingID, Sequencer->GetFocusedTemplateID(), Track->ConditionContainer.Condition, Track, Sequencer->GetSharedPlaybackState()))
{
return EConditionableConditionState::HasConditionEvaluatingTrue;
}
else
{
return EConditionableConditionState::HasConditionEvaluatingFalse;
}
}
// Special case. If we support multiple rows, and there is only a single row, then we must also check track row metadata for a condition here, as there will be no track row model.
if (Track->SupportsMultipleRows() && Track->GetMaxRowIndex() == 0)
{
if (const FMovieSceneTrackRowMetadata* TrackRowMetadata = Track->FindTrackRowMetadata(GetRowIndex()))
{
if (TrackRowMetadata->ConditionContainer.Condition)
{
if (TrackRowMetadata->ConditionContainer.Condition->bEditorForceTrue)
{
return EConditionableConditionState::HasConditionEditorForceTrue;
}
else if (MovieSceneHelpers::EvaluateSequenceCondition(BindingID, Sequencer->GetFocusedTemplateID(), TrackRowMetadata->ConditionContainer.Condition, Track, Sequencer->GetSharedPlaybackState()))
{
return EConditionableConditionState::HasConditionEvaluatingTrue;
}
else
{
return EConditionableConditionState::HasConditionEvaluatingFalse;
}
}
}
}
}
}
return EConditionableConditionState::None;
}
void FTrackModel::SetConditionEditorForceTrue(bool bEditorForceTrue)
{
UMovieSceneTrack* const Track = GetTrack();
if (IsValid(Track))
{
if (Track->ConditionContainer.Condition)
{
const FScopedTransaction Transaction(NSLOCTEXT("SequencerTrackNode", "ConditionEditorForceTrue", "Set Condition Editor Force True"));
Track->ConditionContainer.Condition->Modify();
Track->ConditionContainer.Condition->bEditorForceTrue = bEditorForceTrue;
}
}
}
bool FTrackModel::CanDrag() const
{
// Can only drag root tracks at the moment
TSharedPtr<IObjectBindingExtension> ObjectBindingExtension = FindAncestorOfType<IObjectBindingExtension>();
return ObjectBindingExtension == nullptr;
}
void FTrackModel::BuildContextMenu(FMenuBuilder& MenuBuilder)
{
const TSharedPtr<FSequencerEditorViewModel> EditorViewModel = GetEditor();
if (!EditorViewModel.IsValid())
{
return;
}
const TSharedPtr<FSequencer> Sequencer = EditorViewModel->GetSequencerImpl();
if (!Sequencer.IsValid())
{
return;
}
UMovieSceneTrack* const Track = GetTrack();
if (!IsValid(Track))
{
return;
}
if (TrackEditor)
{
TrackEditor->BuildTrackContextMenu(MenuBuilder, Track);
}
TArray<TWeakObjectPtr<>> WeakTracks;
WeakTracks.Add(Track);
SequencerHelpers::BuildEditTrackMenu(Sequencer, WeakTracks, MenuBuilder, true);
if (Track->GetSupportedBlendTypes().Num() > 0)
{
SequencerHelpers::BuildNewSectionMenu(Sequencer, GetRowIndex() + 1, GetTrack(), MenuBuilder);
}
SequencerHelpers::BuildBlendingMenu(Sequencer, Track, MenuBuilder);
const TArray<TWeakObjectPtr<>> TrackAreaModels = SequencerHelpers::GetSectionObjectsFromTrackAreaModels(GetTrackAreaModelList());
SequencerHelpers::BuildEditSectionMenu(Sequencer, TrackAreaModels, MenuBuilder, true);
if (const TViewModelPtr<FChannelGroupModel> ChannelGroup = TopLevelChannelList.GetHead().ImplicitCast())
{
ChannelGroup->BuildChannelOverrideMenu(MenuBuilder);
}
FOutlinerItemModel::BuildContextMenu(MenuBuilder);
}
void FTrackModel::BuildSidebarMenu(FMenuBuilder& MenuBuilder)
{
const TSharedPtr<FSequencerEditorViewModel> EditorViewModel = GetEditor();
if (!EditorViewModel.IsValid())
{
return;
}
const TSharedPtr<FSequencer> Sequencer = EditorViewModel->GetSequencerImpl();
if (!Sequencer.IsValid())
{
return;
}
UMovieSceneTrack* const Track = GetTrack();
if (!IsValid(Track))
{
return;
}
if (TrackEditor)
{
TrackEditor->BuildTrackSidebarMenu(MenuBuilder, Track);
}
TArray<TWeakObjectPtr<>> WeakTracks;
WeakTracks.Add(Track);
SequencerHelpers::BuildEditTrackMenu(Sequencer, WeakTracks, MenuBuilder, false);
if (Track->GetSupportedBlendTypes().Num() > 0)
{
SequencerHelpers::BuildNewSectionMenu(Sequencer, GetRowIndex() + 1, GetTrack(), MenuBuilder);
}
SequencerHelpers::BuildBlendingMenu(Sequencer, Track, MenuBuilder);
const TArray<TWeakObjectPtr<>> TrackAreaModels = SequencerHelpers::GetSectionObjectsFromTrackAreaModels(GetTrackAreaModelList());
SequencerHelpers::BuildEditSectionMenu(Sequencer, TrackAreaModels, MenuBuilder, false);
if (const TViewModelPtr<FChannelGroupModel> ChannelGroup = TopLevelChannelList.GetHead().ImplicitCast())
{
ChannelGroup->BuildChannelOverrideMenu(MenuBuilder);
}
FOutlinerItemModel::BuildSidebarMenu(MenuBuilder);
}
bool FTrackModel::CanDelete(FText* OutErrorMessage) const
{
return true;
}
void FTrackModel::Delete()
{
UMovieSceneTrack* Track = GetTrack();
if (!Track)
{
return;
}
// Remove from a parent folder if necessary.
if (TViewModelPtr<FFolderModel> ParentFolder = CastParent<FFolderModel>())
{
ParentFolder->GetFolder()->Modify();
ParentFolder->GetFolder()->RemoveChildTrack(Track);
}
TSharedPtr<FSequenceModel> OwnerModel = FindAncestorOfType<FSequenceModel>();
TSharedPtr<IObjectBindingExtension> ParentObjectBinding = FindAncestorOfType<IObjectBindingExtension>();
check(OwnerModel);
UMovieScene* MovieScene = OwnerModel->GetMovieScene();
MovieScene->Modify();
if (ParentObjectBinding)
{
FMovieSceneBinding* Binding = MovieScene->FindBinding(ParentObjectBinding->GetObjectGuid());
if (Binding)
{
Binding->RemoveTrack(*Track, MovieScene);
}
}
else if (MovieScene->GetCameraCutTrack() == Track)
{
MovieScene->RemoveCameraCutTrack();
}
else
{
MovieScene->RemoveTrack(*Track);
}
}
bool FTrackModel::IsMuted() const
{
UMovieSceneTrack* Track = GetTrack();
if (!Track)
{
return false;
}
if (UMovieSceneMuteSoloDecoration* MuteSoloDecoration = Track->FindDecoration<UMovieSceneMuteSoloDecoration>())
{
return MuteSoloDecoration->IsMuted();
}
return false;
}
void FTrackModel::SetIsMuted(bool bIsMuted)
{
UMovieSceneTrack* Track = GetTrack();
if (!Track)
{
return;
}
const bool bAlwaysMarkDirty = false;
Track->Modify(bAlwaysMarkDirty);
if (UMovieSceneMuteSoloDecoration* MuteSoloDecoration = Track->GetOrCreateDecoration<UMovieSceneMuteSoloDecoration>())
{
MuteSoloDecoration->SetMuted(bIsMuted);
}
}
bool FTrackModel::IsSolo() const
{
UMovieSceneTrack* Track = GetTrack();
if (!Track)
{
return false;
}
if (UMovieSceneMuteSoloDecoration* MuteSoloDecoration = Track->FindDecoration<UMovieSceneMuteSoloDecoration>())
{
return MuteSoloDecoration->IsSoloed();
}
return false;
}
void FTrackModel::SetIsSoloed(bool bIsSoloed)
{
UMovieSceneTrack* Track = GetTrack();
if (!Track)
{
return;
}
const bool bAlwaysMarkDirty = false;
Track->Modify(bAlwaysMarkDirty);
if (UMovieSceneMuteSoloDecoration* MuteSoloDecoration = Track->GetOrCreateDecoration<UMovieSceneMuteSoloDecoration>())
{
MuteSoloDecoration->SetSoloed(bIsSoloed);
}
}
bool FTrackModel::FindBoundObjects(TArray<UObject*>& OutBoundObjects) const
{
TSharedPtr<FSequenceModel> SequenceModel = FindAncestorOfType<FSequenceModel>();
TSharedPtr<ISequencer> Sequencer = SequenceModel ? SequenceModel->GetSequencer() : nullptr;
if (!Sequencer)
{
return false;
}
TSharedPtr<IObjectBindingExtension> ParentBinding = FindAncestorOfType<IObjectBindingExtension>();
if (!ParentBinding)
{
return false;
}
TArrayView<TWeakObjectPtr<>> FoundBoundObjects = Sequencer->FindBoundObjects(ParentBinding->GetObjectGuid(), Sequencer->GetFocusedTemplateID());
OutBoundObjects.Reserve(OutBoundObjects.Num() + FoundBoundObjects.Num());
for (TWeakObjectPtr<> WeakObject : FoundBoundObjects)
{
if (UObject* Object = WeakObject.Get())
{
OutBoundObjects.Add(Object);
}
}
return true;
}
} // namespace UE::Sequencer
#undef LOCTEXT_NAMESPACE