// 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 FTrackModel::GetTrackEditor() const { return TrackEditor; } void FTrackModel::OnConstruct() { UMovieSceneTrack* Track = GetTrack(); TSharedPtr SequenceModel = FindAncestorOfType(); 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 SequenceModel = FindAncestorOfType(); if (!SequenceModel) { // Not part of a full sequence hierarchy yet - wait for OnSetSharedData() return; } FSectionModelStorageExtension* SectionModelStorage = SequenceModel->CastDynamic(); FGuid ObjectBinding; if (TSharedPtr ObjectBindingExtension = FindAncestorOfType()) { 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 SectionsTail; FScopedViewModelListHead RecycledModels(AsShared(), EViewModelListType::Recycled); GetChildrenForList(&SectionList).MoveChildrenTo(RecycledModels.GetChildren(), IRecyclableExtension::CallOnRecycle); bool bNeedsLayout = NumRows != PreviousLayoutNumRows; //Sections that weren't expanded TSet> CollapsedSections; // Add all sections directly to this track row for (UMovieSceneSection* Section : Track->GetAllSections()) { TSharedPtr SectionModel = SectionModelStorage->FindModelForSection(Section); if (!SectionModel && TrackEditor) { TSharedRef SectionInterface = TrackEditor->MakeSectionInterface(*Section, *Track, ObjectBinding); SectionModel = SectionModelStorage->CreateModelForSection(Section, SectionInterface); bNeedsLayout = true; } else { TViewModelPtr Outliner = SectionModel->FindAncestorOfType(); 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(RecycledModels.GetChildren(), IRecyclableExtension::CallOnRecycle); TopLevelChannelChildren.MoveChildrenTo(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 Section : TViewModelListIterator(&SectionList)) { LayoutBuilder.RefreshLayout(Section); if (CollapsedSections.Contains(Section)) { TViewModelPtr Outliner = Section->FindAncestorOfType(); 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(RecycledModels.GetChildren(), IRecyclableExtension::CallOnRecycle); OutlinerChildren.MoveChildrenTo(RecycledModels.GetChildren(), IRecyclableExtension::CallOnRecycle); TopLevelChannelChildren.MoveChildrenTo(RecycledModels.GetChildren(), IRecyclableExtension::CallOnRecycle); // We need to build row models so let's grab the storage for that FTrackRowModelStorageExtension* TrackRowModelStorage = SequenceModel->CastDynamic(); 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 Row; TSharedPtr SectionsTail; TUniquePtr RecycledModels; bool bNeedsLayout = false; }; TArray> RowModels; RowModels.SetNum(PopulatedRows.Num()); // Create track row models for all populated rows TSharedPtr LastTrackRowModel; for (TConstSetBitIterator<> It(PopulatedRows); It; ++It) { const int32 RowIndex = It.GetIndex(); RowModels[RowIndex].bNeedsLayout = NumRows != PreviousLayoutNumRows; TSharedPtr 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(TrackRowModel, EViewModelListType::Recycled); FViewModelChildren RecycledRowModels = RowModels[RowIndex].RecycledModels->GetChildren(); TrackRowModel->GetSectionModels().MoveChildrenTo(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 SectionModel = SectionModelStorage->FindModelForSection(Section); if (!SectionModel && TrackEditor) { TSharedRef 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(RecycledRowModels, IRecyclableExtension::CallOnRecycle); RowData.Row->GetTopLevelChannels().MoveChildrenTo(RecycledRowModels, IRecyclableExtension::CallOnRecycle); FTrackModelLayoutBuilder LayoutBuilder(RowData.Row->AsShared()); for (TSharedPtr Section : RowData.Row->GetChildrenOfType(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 Section : SectionList.Iterate()) { 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(GetTrack()); return NameableTrack && NameableTrack->CanRename(); } void FTrackModel::Rename(const FText& NewName) { UMovieSceneNameableTrack* NameableTrack = ::Cast(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 OwnerModel = FindAncestorOfType()) { OwnerModel->GetSequencer()->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::TrackValueChanged); } } } bool FTrackModel::IsRenameValidImpl(const FText& NewName, FText& OutErrorMessage) const { UMovieSceneNameableTrack* NameableTrack = ::Cast(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() != 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(this)->GetTopLevelChannels(); for (auto It(TopLevelChannels.IterateSubList()); It; ++It) { if (It->HasCurves()) { return true; } } return false; } void FTrackModel::CreateCurveModels(TArray>& OutCurveModels) { TViewModelPtr ChannelGroup = TopLevelChannelList.GetHead().ImplicitCast(); if (ChannelGroup) { ChannelGroup->CreateCurveModels(OutCurveModels); } } bool FTrackModel::GetDefaultExpansionState() const { TViewModelListIterator It = GetChildrenOfType(); 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 ObjectBindingModel = FindAncestorOfType()) { BindingID = ObjectBindingModel->GetObjectGuid(); } if (TViewModelPtr SequenceModel = FindAncestorOfType()) { SequenceID = SequenceModel->GetSequenceID(); if (TSharedPtr 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 TopLevelChannel = TopLevelChannelList.GetHead().ImplicitCast(); if (TopLevelChannel) { for (const TViewModelPtr& ChannelModel : TopLevelChannel->GetTrackAreaModelListAs()) { 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 SequenceModel = FindAncestorOfType()) { if (TSharedPtr SequencerModel = SequenceModel->GetEditor()) { LabelParams.SequenceID = SequenceModel->GetSequenceID(); LabelParams.Player = SequencerModel->GetSequencer().Get(); if (LabelParams.Player) { if (TViewModelPtr ObjectBindingModel = FindAncestorOfType()) { 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 > 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 SequenceModel = FindAncestorOfType()) { if (TSharedPtr SequencerModel = SequenceModel->GetEditor()) { LabelParams.SequenceID = SequenceModel->GetSequenceID(); LabelParams.Player = SequencerModel->GetSequencer().Get(); if (LabelParams.Player) { if (TViewModelPtr ObjectBindingModel = FindAncestorOfType()) { LabelParams.BindingID = ObjectBindingModel->GetObjectGuid(); } return Track->GetDisplayNameToolTipText(LabelParams); } } } return FText(); } TSharedPtr 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& Section : SectionList.Iterate()) { ++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& Section : SectionList.Iterate()) { 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 SequenceModel = FindAncestorOfType(); TSharedPtr Sequencer = SequenceModel ? SequenceModel->GetSequencer() : nullptr; if (Sequencer) { FGuid BindingID; if (TSharedPtr ParentBinding = FindAncestorOfType()) { 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 ObjectBindingExtension = FindAncestorOfType(); return ObjectBindingExtension == nullptr; } void FTrackModel::BuildContextMenu(FMenuBuilder& MenuBuilder) { const TSharedPtr EditorViewModel = GetEditor(); if (!EditorViewModel.IsValid()) { return; } const TSharedPtr Sequencer = EditorViewModel->GetSequencerImpl(); if (!Sequencer.IsValid()) { return; } UMovieSceneTrack* const Track = GetTrack(); if (!IsValid(Track)) { return; } if (TrackEditor) { TrackEditor->BuildTrackContextMenu(MenuBuilder, Track); } TArray> 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> TrackAreaModels = SequencerHelpers::GetSectionObjectsFromTrackAreaModels(GetTrackAreaModelList()); SequencerHelpers::BuildEditSectionMenu(Sequencer, TrackAreaModels, MenuBuilder, true); if (const TViewModelPtr ChannelGroup = TopLevelChannelList.GetHead().ImplicitCast()) { ChannelGroup->BuildChannelOverrideMenu(MenuBuilder); } FOutlinerItemModel::BuildContextMenu(MenuBuilder); } void FTrackModel::BuildSidebarMenu(FMenuBuilder& MenuBuilder) { const TSharedPtr EditorViewModel = GetEditor(); if (!EditorViewModel.IsValid()) { return; } const TSharedPtr Sequencer = EditorViewModel->GetSequencerImpl(); if (!Sequencer.IsValid()) { return; } UMovieSceneTrack* const Track = GetTrack(); if (!IsValid(Track)) { return; } if (TrackEditor) { TrackEditor->BuildTrackSidebarMenu(MenuBuilder, Track); } TArray> 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> TrackAreaModels = SequencerHelpers::GetSectionObjectsFromTrackAreaModels(GetTrackAreaModelList()); SequencerHelpers::BuildEditSectionMenu(Sequencer, TrackAreaModels, MenuBuilder, false); if (const TViewModelPtr 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 ParentFolder = CastParent()) { ParentFolder->GetFolder()->Modify(); ParentFolder->GetFolder()->RemoveChildTrack(Track); } TSharedPtr OwnerModel = FindAncestorOfType(); TSharedPtr ParentObjectBinding = FindAncestorOfType(); 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()) { 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()) { MuteSoloDecoration->SetMuted(bIsMuted); } } bool FTrackModel::IsSolo() const { UMovieSceneTrack* Track = GetTrack(); if (!Track) { return false; } if (UMovieSceneMuteSoloDecoration* MuteSoloDecoration = Track->FindDecoration()) { 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()) { MuteSoloDecoration->SetSoloed(bIsSoloed); } } bool FTrackModel::FindBoundObjects(TArray& OutBoundObjects) const { TSharedPtr SequenceModel = FindAncestorOfType(); TSharedPtr Sequencer = SequenceModel ? SequenceModel->GetSequencer() : nullptr; if (!Sequencer) { return false; } TSharedPtr ParentBinding = FindAncestorOfType(); if (!ParentBinding) { return false; } TArrayView> 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