// Copyright Epic Games, Inc. All Rights Reserved. #include "StateTreeViewModel.h" #include "Templates/SharedPointer.h" #include "StateTree.h" #include "StateTreeEditorData.h" #include "StateTreeState.h" #include "StateTreeTaskBase.h" #include "StateTreeDelegates.h" #include "Editor.h" #include "EditorSupportDelegates.h" #include "ScopedTransaction.h" #include "Modules/ModuleManager.h" #include "StateTreeEditorModule.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #define LOCTEXT_NAMESPACE "StateTreeEditor" namespace UE::StateTree::Editor { // Removes states from the array which are children of any other state. void RemoveContainedChildren(TArray& States) { TSet UniqueStates; for (UStateTreeState* State : States) { UniqueStates.Add(State); } for (int32 i = 0; i < States.Num(); ) { UStateTreeState* State = States[i]; // Walk up the parent state sand if the current state // exists in any of them, remove it. UStateTreeState* StateParent = State->Parent; bool bShouldRemove = false; while (StateParent) { if (UniqueStates.Contains(StateParent)) { bShouldRemove = true; break; } StateParent = StateParent->Parent; } if (bShouldRemove) { States.RemoveAt(i); } else { i++; } } } // Returns true if the state is child of parent state. bool IsChildOf(const UStateTreeState* ParentState, const UStateTreeState* State) { for (const UStateTreeState* Child : ParentState->Children) { if (Child == State) { return true; } if (IsChildOf(Child, State)) { return true; } } return false; } }; FStateTreeViewModel::FStateTreeViewModel() : TreeData(nullptr) { } FStateTreeViewModel::~FStateTreeViewModel() { GEditor->UnregisterForUndo(this); UE::StateTree::Delegates::OnIdentifierChanged.RemoveAll(this); } void FStateTreeViewModel::Init(UStateTreeEditorData* InTreeData) { TreeData = InTreeData; GEditor->RegisterForUndo(this); UE::StateTree::Delegates::OnIdentifierChanged.AddSP(this, &FStateTreeViewModel::HandleIdentifierChanged); } void FStateTreeViewModel::HandleIdentifierChanged(const UStateTree& StateTree) const { const UStateTree* OuterStateTree = TreeData ? Cast(TreeData->GetOuter()) : nullptr; if (OuterStateTree == &StateTree) { OnAssetChanged.Broadcast(); } } void FStateTreeViewModel::NotifyAssetChangedExternally() const { OnAssetChanged.Broadcast(); } void FStateTreeViewModel::NotifyStatesChangedExternally(const TSet& ChangedStates, const FPropertyChangedEvent& PropertyChangedEvent) const { OnStatesChanged.Broadcast(ChangedStates, PropertyChangedEvent); } TArray* FStateTreeViewModel::GetSubTrees() const { return TreeData != nullptr ? &TreeData->SubTrees : nullptr; } int32 FStateTreeViewModel::GetSubTreeCount() const { return TreeData != nullptr ? TreeData->SubTrees.Num() : 0; } void FStateTreeViewModel::PostUndo(bool bSuccess) { // TODO: see if we can narrow this down. OnAssetChanged.Broadcast(); } void FStateTreeViewModel::PostRedo(bool bSuccess) { OnAssetChanged.Broadcast(); } void FStateTreeViewModel::ClearSelection() { SelectedStates.Reset(); const TArray SelectedStatesArr; OnSelectionChanged.Broadcast(SelectedStatesArr); } void FStateTreeViewModel::SetSelection(UStateTreeState* Selected) { SelectedStates.Reset(); SelectedStates.Add(Selected); TArray SelectedStatesArr; SelectedStatesArr.Add(Selected); OnSelectionChanged.Broadcast(SelectedStatesArr); } void FStateTreeViewModel::SetSelection(const TArray& InSelectedStates) { SelectedStates.Reset(); for (UStateTreeState* State : InSelectedStates) { if (State) { SelectedStates.Add(State); } } TArray SelectedTaskIDArr; OnSelectionChanged.Broadcast(InSelectedStates); } bool FStateTreeViewModel::IsSelected(const UStateTreeState* State) const { return SelectedStates.Contains(State); } bool FStateTreeViewModel::IsChildOfSelection(const UStateTreeState* State) const { for (const FWeakObjectPtr& WeakSelectedState : SelectedStates) { if (const UStateTreeState* SelectedState = Cast(WeakSelectedState.Get())) { if (SelectedState == State) { return true; } if (UE::StateTree::Editor::IsChildOf(SelectedState, State)) { return true; } } } return false; } void FStateTreeViewModel::GetSelectedStates(TArray& OutSelectedStates) { OutSelectedStates.Reset(); for (FWeakObjectPtr& WeakState : SelectedStates) { if (UStateTreeState* State = Cast(WeakState.Get())) { OutSelectedStates.Add(State); } } } bool FStateTreeViewModel::HasSelection() const { return SelectedStates.Num() > 0; } void FStateTreeViewModel::GetPersistentExpandedStates(TSet& OutExpandedStates) { OutExpandedStates.Reset(); if (TreeData == nullptr) { return; } for (UStateTreeState* SubTree : TreeData->SubTrees) { GetExpandedStatesRecursive(SubTree, OutExpandedStates); } } void FStateTreeViewModel::GetExpandedStatesRecursive(UStateTreeState* State, TSet& OutExpandedStates) { if (State->bExpanded) { OutExpandedStates.Add(State); } for (UStateTreeState* Child : State->Children) { GetExpandedStatesRecursive(Child, OutExpandedStates); } } void FStateTreeViewModel::SetPersistentExpandedStates(TSet& InExpandedStates) { if (TreeData == nullptr) { return; } TreeData->Modify(); for (UStateTreeState* State : InExpandedStates) { if (State != nullptr) { State->bExpanded = true; } } } void FStateTreeViewModel::AddState(UStateTreeState* AfterState) { if (TreeData == nullptr) { return; } const FScopedTransaction Transaction(LOCTEXT("AddStateTransaction", "Add State")); UStateTreeState* NewState = NewObject(TreeData, FName(), RF_Transactional); UStateTreeState* ParentState = nullptr; if (AfterState != nullptr) { ParentState = AfterState->Parent; if (ParentState != nullptr) { ParentState->Modify(); } else { TreeData->Modify(); } TArray& ParentArray = ParentState ? ParentState->Children : TreeData->SubTrees; const int32 TargetIndex = ParentArray.Find(AfterState); if (TargetIndex != INDEX_NONE) { // Insert After ParentArray.Insert(NewState, TargetIndex + 1); NewState->Parent = ParentState; } else { // Fallback, should never happen. ensureMsgf(false, TEXT("%s: Failed to find specified target state %s on state %s while adding new state."), *GetNameSafe(TreeData->GetOuter()), *GetNameSafe(AfterState), *GetNameSafe(ParentState)); ParentArray.Add(NewState); NewState->Parent = ParentState; } } OnStateAdded.Broadcast(ParentState, NewState); } void FStateTreeViewModel::AddChildState(UStateTreeState* ParentState) { if (TreeData == nullptr || ParentState == nullptr) { return; } const FScopedTransaction Transaction(LOCTEXT("AddChildStateTransaction", "Add Child State")); UStateTreeState* NewState = NewObject(ParentState, FName(), RF_Transactional); ParentState->Modify(); ParentState->Children.Add(NewState); NewState->Parent = ParentState; OnStateAdded.Broadcast(ParentState, NewState); } void FStateTreeViewModel::RenameState(UStateTreeState* State, FName NewName) { if (State == nullptr) { return; } const FScopedTransaction Transaction(LOCTEXT("RenameTransaction", "Rename")); State->Modify(); State->Name = NewName; TSet AffectedStates; AffectedStates.Add(State); FProperty* NameProperty = FindFProperty(UStateTreeState::StaticClass(), GET_MEMBER_NAME_CHECKED(UStateTreeState, Name)); FPropertyChangedEvent PropertyChangedEvent(NameProperty, EPropertyChangeType::ValueSet); OnStatesChanged.Broadcast(AffectedStates, PropertyChangedEvent); } void FStateTreeViewModel::RemoveSelectedStates() { if (TreeData == nullptr) { return; } TArray States; GetSelectedStates(States); // Remove items whose parent also exists in the selection. UE::StateTree::Editor::RemoveContainedChildren(States); if (States.Num() > 0) { const FScopedTransaction Transaction(LOCTEXT("DeleteStateTransaction", "Delete State")); TSet AffectedParents; for (UStateTreeState* StateToRemove : States) { if (StateToRemove) { StateToRemove->Modify(); UStateTreeState* ParentState = StateToRemove->Parent; if (ParentState != nullptr) { AffectedParents.Add(ParentState); ParentState->Modify(); } else { AffectedParents.Add(nullptr); TreeData->Modify(); } TArray& ArrayToRemoveFrom = ParentState ? ParentState->Children : TreeData->SubTrees; const int32 ItemIndex = ArrayToRemoveFrom.Find(StateToRemove); if (ItemIndex != INDEX_NONE) { ArrayToRemoveFrom.RemoveAt(ItemIndex); StateToRemove->Parent = nullptr; } } } OnStatesRemoved.Broadcast(AffectedParents); ClearSelection(); } } void FStateTreeViewModel::MoveSelectedStatesBefore(UStateTreeState* TargetState) { MoveSelectedStates(TargetState, FStateTreeViewModelInsert::Before); } void FStateTreeViewModel::MoveSelectedStatesAfter(UStateTreeState* TargetState) { MoveSelectedStates(TargetState, FStateTreeViewModelInsert::After); } void FStateTreeViewModel::MoveSelectedStatesInto(UStateTreeState* TargetState) { MoveSelectedStates(TargetState, FStateTreeViewModelInsert::Into); } void FStateTreeViewModel::MoveSelectedStates(UStateTreeState* TargetState, const FStateTreeViewModelInsert RelativeLocation) { if (TreeData == nullptr || TargetState == nullptr) { return; } TArray States; GetSelectedStates(States); // Remove child items whose parent also exists in the selection. UE::StateTree::Editor::RemoveContainedChildren(States); // Remove states which contain target state as child. States.RemoveAll([TargetState](const UStateTreeState* State) { return UE::StateTree::Editor::IsChildOf(State, TargetState); }); if (States.Num() > 0 && TargetState != nullptr) { const FScopedTransaction Transaction(LOCTEXT("MoveTransaction", "Move")); TSet AffectedParents; TSet AffectedStates; UStateTreeState* TargetParent = TargetState->Parent; if (RelativeLocation == FStateTreeViewModelInsert::Into) { AffectedParents.Add(TargetState); } else { AffectedParents.Add(TargetParent); } for (int32 i = States.Num() - 1; i >= 0; i--) { if (UStateTreeState* State = States[i]) { State->Modify(); if (State->Parent) { AffectedParents.Add(State->Parent); } } } for (UStateTreeState* Parent : AffectedParents) { if (Parent) { Parent->Modify(); } else { TreeData->Modify(); } } // Add in reverse order to keep the original order. for (int32 i = States.Num() - 1; i >= 0; i--) { if (UStateTreeState* SelectedState = States[i]) { AffectedStates.Add(SelectedState); UStateTreeState* SelectedParent = SelectedState->Parent; // Remove from current parent TArray& ArrayToRemoveFrom = SelectedParent ? SelectedParent->Children : TreeData->SubTrees; const int32 ItemIndex = ArrayToRemoveFrom.Find(SelectedState); if (ItemIndex != INDEX_NONE) { ArrayToRemoveFrom.RemoveAt(ItemIndex); SelectedState->Parent = nullptr; } // Insert to new parent if (RelativeLocation == FStateTreeViewModelInsert::Into) { // Into TargetState->Children.Insert(SelectedState, /*Index*/0); SelectedState->Parent = TargetState; } else { TArray& ArrayToMoveTo = TargetParent ? TargetParent->Children : TreeData->SubTrees; const int32 TargetIndex = ArrayToMoveTo.Find(TargetState); if (TargetIndex != INDEX_NONE) { if (RelativeLocation == FStateTreeViewModelInsert::Before) { // Before ArrayToMoveTo.Insert(SelectedState, TargetIndex); SelectedState->Parent = TargetParent; } else if (RelativeLocation == FStateTreeViewModelInsert::After) { // After ArrayToMoveTo.Insert(SelectedState, TargetIndex + 1); SelectedState->Parent = TargetParent; } } else { // Fallback, should never happen. ensureMsgf(false, TEXT("%s: Failed to find specified target state %s on state %s while moving a state."), *GetNameSafe(TreeData->GetOuter()), *GetNameSafe(TargetState), *GetNameSafe(SelectedParent)); ArrayToMoveTo.Add(SelectedState); SelectedState->Parent = TargetParent; } } } } OnStatesMoved.Broadcast(AffectedParents, AffectedStates); SetSelection(States); } } #undef LOCTEXT_NAMESPACE