// Copyright Epic Games, Inc. All Rights Reserved. #include "MVVM/CurveEditorIntegrationExtension.h" #include "MVVM/CurveEditorExtension.h" #include "MVVM/TrackModelStorageExtension.h" #include "MVVM/ViewModels/ViewModel.h" #include "MVVM/SharedViewModelData.h" #include "MVVM/ViewModelPtr.h" #include "MVVM/ViewModels/SequenceModel.h" #include "MVVM/ViewModels/SequencerEditorViewModel.h" #include "MVVM/ViewModels/ViewModelIterators.h" #include "MVVM/Extensions/ICurveEditorTreeItemExtension.h" #include "CurveEditor.h" #include "SequencerSettings.h" namespace UE { namespace Sequencer { FCurveEditorIntegrationExtension::FCurveEditorIntegrationExtension() { } void FCurveEditorIntegrationExtension::OnCreated(TSharedRef InWeakOwner) { ensureMsgf(!WeakOwnerModel.Pin().IsValid(), TEXT("This extension was already created!")); WeakOwnerModel = InWeakOwner->CastThisShared(); FSimpleMulticastDelegate& HierarchyChanged = InWeakOwner->GetSharedData()->SubscribeToHierarchyChanged(InWeakOwner); HierarchyChanged.AddSP(this, &FCurveEditorIntegrationExtension::OnHierarchyChanged); } void FCurveEditorIntegrationExtension::OnHierarchyChanged() { UpdateCurveEditor(); } FCurveEditorExtension* FCurveEditorIntegrationExtension::GetCurveEditorExtension() { if (TSharedPtr OwnerModel = WeakOwnerModel.Pin()) { if (TSharedPtr EditorViewModel = OwnerModel->GetEditor()) { return EditorViewModel->CastDynamic(); } } return nullptr; } void FCurveEditorIntegrationExtension::UpdateCurveEditor() { TSharedPtr OwnerModel = WeakOwnerModel.Pin(); if (!OwnerModel) { return; } FCurveEditorExtension* CurveEditorExtension = GetCurveEditorExtension(); if (!CurveEditorExtension) { return; } TSharedPtr CurveEditor = CurveEditorExtension->GetCurveEditor(); if (!CurveEditor) { return; } const bool bUseFilteredOut = OwnerModel->GetSequencer()->GetSequencerSettings()->GetLinkFiltersWithCurveEditor(); FCurveEditorTree* CurveEditorTree = CurveEditor->GetTree(); // Guard against multiple broadcasts here and defer them until the end of this function FScopedCurveEditorTreeEventGuard ScopedEventGuard = CurveEditorTree->ScopedEventGuard(); // Remove any stale tree items for (auto It = ViewModelToTreeItemIDMap.CreateIterator(); It; ++It) { bool bIsRelevant = false; const FCurveEditorTreeItemID ItemID(It.Value()); TViewModelPtr ViewModel = CastViewModel(It.Key().Pin()); if (ViewModel) { TViewModelPtr OutlinerModel = ViewModel.ImplicitCast(); const bool bIsVisible = bUseFilteredOut ? OutlinerModel && !OutlinerModel->IsFilteredOut() : true; const FCurveEditorTreeItem* TreeItem = CurveEditorTree->FindItem(ItemID); FCurveEditorTreeItemID ParentID; if (auto Parent = ViewModel.AsModel()->FindAncestorOfType()) { ParentID = ViewModelToTreeItemIDMap.FindRef(Parent.AsModel()); } bIsRelevant = bIsVisible && TreeItem && TreeItem->GetParentID() == ParentID; } if (!bIsRelevant) { if (ViewModel) { ViewModel->OnRemovedFromCurveEditor(CurveEditor); } CurveEditor->RemoveTreeItem(ItemID); It.RemoveCurrent(); } } // Do a second pass to remove any items that were removed recursively above for (auto It = ViewModelToTreeItemIDMap.CreateIterator(); It; ++It) { if (CurveEditorTree->FindItem(It->Value) == nullptr) { It.RemoveCurrent(); } } // Iterate all non-filtered out outliners and check for curve editor tree extensions const bool bIncludeRootNode = false; bool bItemsAdded = false; for (TParentFirstChildIterator It(OwnerModel, bIncludeRootNode); It; ++It) { if (bUseFilteredOut && It->IsFilteredOut()) { It.IgnoreCurrentChildren(); continue; } if (TViewModelPtr ChildViewModel = (*It).ImplicitCast()) { const FCurveEditorTreeItemID ItemID = ViewModelToTreeItemIDMap.FindRef(ChildViewModel.AsModel()); if (!ItemID.IsValid()) { bItemsAdded = true; AddToCurveEditor(ChildViewModel, CurveEditor); } } } // If new items have been added, synchronize selection after the view models have been added to the curve editor. Synchronization depends on curve model tree item IDs if (bItemsAdded) { CurveEditorExtension->RequestSyncSelection(); } } FCurveEditorTreeItemID FCurveEditorIntegrationExtension::AddToCurveEditor(TViewModelPtr InViewModel, TSharedPtr InCurveEditor) { // If the view model doesn't want to be in the curve editor, bail out // Note that this means we will create curve editor items for each parent in the hierarchy up // until the first parent that doesn't implement ICurveEditorTreeItemExtension // That is: we don't create "dummy" entries when there's a "gap" in the hierarchy if (!InViewModel) { return FCurveEditorTreeItemID::Invalid(); } // Check if we already have a valid curve editor ID if (FCurveEditorTreeItemID* Existing = ViewModelToTreeItemIDMap.Find(InViewModel.AsModel())) { if (InCurveEditor->GetTree()->FindItem(*Existing) != nullptr) { return *Existing; } } // Recursively create any needed parent curve editor items TViewModelPtr Parent = InViewModel.AsModel()->CastParent(); FCurveEditorTreeItemID ParentID = Parent ? AddToCurveEditor(Parent, InCurveEditor) : FCurveEditorTreeItemID::Invalid(); // Create the new curve editor item FCurveEditorTreeItem* NewItem = InCurveEditor->AddTreeItem(ParentID); TSharedPtr CurveEditorTreeItem = InViewModel->GetCurveEditorTreeItem(); NewItem->SetWeakItem(CurveEditorTreeItem); TOptional UniquePathName = InViewModel->GetUniquePathName(); NewItem->SetUniquePathName(UniquePathName); // Register the new ID in our map and notify the view model ViewModelToTreeItemIDMap.Add(InViewModel.AsModel(), NewItem->GetID()); InViewModel->OnAddedToCurveEditor(NewItem->GetID(), InCurveEditor); return NewItem->GetID(); } void FCurveEditorIntegrationExtension::ResetCurveEditor() { FCurveEditorExtension* CurveEditorExtension = GetCurveEditorExtension(); if (!ensure(CurveEditorExtension)) { return; } TSharedPtr CurveEditor = CurveEditorExtension->GetCurveEditor(); if (!ensure(CurveEditor)) { return; } for (TPair, FCurveEditorTreeItemID> Pair : ViewModelToTreeItemIDMap) { FCurveEditorTreeItemID ItemID(Pair.Value); TViewModelPtr ViewModel = CastViewModel(Pair.Key.Pin()); if (ViewModel) { ViewModel->OnRemovedFromCurveEditor(CurveEditor); } CurveEditor->RemoveTreeItem(ItemID); } ViewModelToTreeItemIDMap.Reset(); } } // namespace Sequencer } // namespace UE