Files
UnrealEngine/Engine/Plugins/Runtime/StateTree/Source/StateTreeEditorModule/Private/SStateTreeOutliner.cpp
2025-05-18 13:04:45 +08:00

493 lines
14 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SStateTreeOutliner.h"
#include "StateTreeDelegates.h"
#include "StateTreeEditorCommands.h"
#include "StateTreeEditorData.h"
#include "StateTreeState.h"
#include "StateTreeViewModel.h"
#include "Customizations/Widgets/SCompactStateTreeView.h"
#include "Debugger/StateTreeDebuggerTypes.h"
#include "Framework/Commands/UICommandList.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#define LOCTEXT_NAMESPACE "StateTreeEditor"
SStateTreeOutliner::SStateTreeOutliner()
{
}
SStateTreeOutliner::~SStateTreeOutliner()
{
if (StateTreeViewModel)
{
StateTreeViewModel->GetOnAssetChanged().RemoveAll(this);
StateTreeViewModel->GetOnStatesRemoved().RemoveAll(this);
StateTreeViewModel->GetOnStatesMoved().RemoveAll(this);
StateTreeViewModel->GetOnStateAdded().RemoveAll(this);
StateTreeViewModel->GetOnStatesChanged().RemoveAll(this);
StateTreeViewModel->GetOnSelectionChanged().RemoveAll(this);
}
UE::StateTree::Delegates::OnVisualThemeChanged.RemoveAll(this);
}
void SStateTreeOutliner::Construct(const FArguments& InArgs, TSharedRef<FStateTreeViewModel> InStateTreeViewModel, const TSharedRef<FUICommandList>& InCommandList)
{
StateTreeViewModel = InStateTreeViewModel;
StateTreeViewModel->GetOnAssetChanged().AddSP(this, &SStateTreeOutliner::HandleModelAssetChanged);
StateTreeViewModel->GetOnStatesRemoved().AddSP(this, &SStateTreeOutliner::HandleModelStatesRemoved);
StateTreeViewModel->GetOnStatesMoved().AddSP(this, &SStateTreeOutliner::HandleModelStatesMoved);
StateTreeViewModel->GetOnStateAdded().AddSP(this, &SStateTreeOutliner::HandleModelStateAdded);
StateTreeViewModel->GetOnStatesChanged().AddSP(this, &SStateTreeOutliner::HandleModelStatesChanged);
StateTreeViewModel->GetOnSelectionChanged().AddSP(this, &SStateTreeOutliner::HandleModelSelectionChanged);
UE::StateTree::Delegates::OnVisualThemeChanged.AddSP(this, &SStateTreeOutliner::HandleVisualThemeChanged);
bUpdatingSelection = false;
ChildSlot
[
SAssignNew(CompactStateTreeView, SCompactStateTreeView, StateTreeViewModel)
.SelectionMode(ESelectionMode::Multi)
.StateTreeEditorData(StateTreeViewModel->GetStateTreeEditorData())
.OnSelectionChanged(this, &SStateTreeOutliner::HandleTreeViewSelectionChanged)
.OnContextMenuOpening(this, &SStateTreeOutliner::HandleContextMenuOpening)
.ShowLinkedStates(true)
];
CommandList = InCommandList;
BindCommands();
}
void SStateTreeOutliner::BindCommands()
{
const FStateTreeEditorCommands& Commands = FStateTreeEditorCommands::Get();
CommandList->MapAction(
Commands.AddSiblingState,
FExecuteAction::CreateSP(this, &SStateTreeOutliner::HandleAddSiblingState),
FCanExecuteAction());
CommandList->MapAction(
Commands.AddChildState,
FExecuteAction::CreateSP(this, &SStateTreeOutliner::HandleAddChildState),
FCanExecuteAction::CreateSP(this, &SStateTreeOutliner::HasSelection));
CommandList->MapAction(
Commands.CutStates,
FExecuteAction::CreateSP(this, &SStateTreeOutliner::HandleCutSelectedStates),
FCanExecuteAction::CreateSP(this, &SStateTreeOutliner::HasSelection));
CommandList->MapAction(
Commands.CopyStates,
FExecuteAction::CreateSP(this, &SStateTreeOutliner::HandleCopySelectedStates),
FCanExecuteAction::CreateSP(this, &SStateTreeOutliner::HasSelection));
CommandList->MapAction(
Commands.DeleteStates,
FExecuteAction::CreateSP(this, &SStateTreeOutliner::HandleDeleteStates),
FCanExecuteAction::CreateSP(this, &SStateTreeOutliner::HasSelection));
CommandList->MapAction(
Commands.PasteStatesAsSiblings,
FExecuteAction::CreateSP(this, &SStateTreeOutliner::HandlePasteStatesAsSiblings),
FCanExecuteAction::CreateSP(this, &SStateTreeOutliner::CanPaste));
CommandList->MapAction(
Commands.PasteStatesAsChildren,
FExecuteAction::CreateSP(this, &SStateTreeOutliner::HandlePasteStatesAsChildren),
FCanExecuteAction::CreateSP(this, &SStateTreeOutliner::CanPaste));
CommandList->MapAction(
Commands.DuplicateStates,
FExecuteAction::CreateSP(this, &SStateTreeOutliner::HandleDuplicateSelectedStates),
FCanExecuteAction::CreateSP(this, &SStateTreeOutliner::HasSelection));
CommandList->MapAction(
Commands.EnableStates,
FExecuteAction::CreateSP(this, &SStateTreeOutliner::HandleEnableSelectedStates),
FCanExecuteAction(),
FGetActionCheckState::CreateSPLambda(this, [this]
{
const bool bCanEnable = CanEnableStates();
const bool bCanDisable = CanDisableStates();
if (bCanEnable && bCanDisable)
{
return ECheckBoxState::Undetermined;
}
if (bCanDisable)
{
return ECheckBoxState::Checked;
}
if (bCanEnable)
{
return ECheckBoxState::Unchecked;
}
// Should not happen since action is not visible in this case
return ECheckBoxState::Undetermined;
}),
FIsActionButtonVisible::CreateSPLambda(this, [this]
{
return CanEnableStates() || CanDisableStates();
}));
#if WITH_STATETREE_TRACE_DEBUGGER
CommandList->MapAction(
Commands.EnableOnEnterStateBreakpoint,
FExecuteAction::CreateSPLambda(this, [this]
{
if (StateTreeViewModel)
{
StateTreeViewModel->HandleEnableStateBreakpoint(EStateTreeBreakpointType::OnEnter);
}
}),
FCanExecuteAction(),
FGetActionCheckState::CreateSPLambda(this, [this]
{
return StateTreeViewModel ? StateTreeViewModel->GetStateBreakpointCheckState(EStateTreeBreakpointType::OnEnter) : ECheckBoxState::Unchecked;
}),
FIsActionButtonVisible::CreateSPLambda(this, [this]
{
return (StateTreeViewModel)
&& (StateTreeViewModel->CanAddStateBreakpoint(EStateTreeBreakpointType::OnEnter)
|| StateTreeViewModel->CanRemoveStateBreakpoint(EStateTreeBreakpointType::OnEnter));
}));
CommandList->MapAction(
Commands.EnableOnExitStateBreakpoint,
FExecuteAction::CreateSPLambda(this, [this]
{
if (StateTreeViewModel)
{
StateTreeViewModel->HandleEnableStateBreakpoint(EStateTreeBreakpointType::OnExit);
}
}),
FCanExecuteAction(),
FGetActionCheckState::CreateSPLambda(this, [this]
{
return StateTreeViewModel ? StateTreeViewModel->GetStateBreakpointCheckState(EStateTreeBreakpointType::OnExit) : ECheckBoxState::Unchecked;
}),
FIsActionButtonVisible::CreateSPLambda(this, [this]
{
return (StateTreeViewModel)
&& (StateTreeViewModel->CanAddStateBreakpoint(EStateTreeBreakpointType::OnExit)
|| StateTreeViewModel->CanRemoveStateBreakpoint(EStateTreeBreakpointType::OnExit));
}));
#endif // WITH_STATETREE_TRACE_DEBUGGER
}
void SStateTreeOutliner::HandleModelAssetChanged()
{
bItemsDirty = true;
if (CompactStateTreeView && StateTreeViewModel)
{
CompactStateTreeView->Refresh(StateTreeViewModel->GetStateTreeEditorData());
}
}
void SStateTreeOutliner::HandleModelStatesRemoved(const TSet<UStateTreeState*>& AffectedParents)
{
bItemsDirty = true;
if (CompactStateTreeView && StateTreeViewModel)
{
CompactStateTreeView->Refresh(StateTreeViewModel->GetStateTreeEditorData());
}
}
void SStateTreeOutliner::HandleModelStatesMoved(const TSet<UStateTreeState*>& AffectedParents, const TSet<UStateTreeState*>& MovedStates)
{
bItemsDirty = true;
if (CompactStateTreeView && StateTreeViewModel)
{
CompactStateTreeView->Refresh(StateTreeViewModel->GetStateTreeEditorData());
}
}
void SStateTreeOutliner::HandleModelStateAdded(UStateTreeState* ParentState, UStateTreeState* NewState)
{
bItemsDirty = true;
if (CompactStateTreeView && StateTreeViewModel)
{
CompactStateTreeView->Refresh(StateTreeViewModel->GetStateTreeEditorData());
}
}
void SStateTreeOutliner::HandleModelStatesChanged(const TSet<UStateTreeState*>& AffectedStates, const FPropertyChangedEvent& PropertyChangedEvent)
{
bool bArraysChanged = false;
// The purpose of the rebuild below is to update the task visualization (number of widgets change).
// This method is called when anything in a state changes, make sure to only rebuild when needed.
if (PropertyChangedEvent.MemberProperty->GetFName() == GET_MEMBER_NAME_CHECKED(UStateTreeState, Tasks))
{
bArraysChanged = true;
}
if (bArraysChanged)
{
}
if (CompactStateTreeView && StateTreeViewModel)
{
CompactStateTreeView->Refresh(StateTreeViewModel->GetStateTreeEditorData());
}
}
void SStateTreeOutliner::HandleModelSelectionChanged(const TArray<TWeakObjectPtr<UStateTreeState>>& SelectedStates)
{
if (bUpdatingSelection)
{
return;
}
if (CompactStateTreeView && StateTreeViewModel)
{
TArray<FGuid> StateIDs;
for (const TWeakObjectPtr<UStateTreeState>& WeakState : SelectedStates)
{
if (const UStateTreeState* State = WeakState.Get())
{
StateIDs.Add(State->ID);
}
}
CompactStateTreeView->SetSelection(StateIDs);
}
}
void SStateTreeOutliner::HandleTreeViewSelectionChanged(TConstArrayView<FGuid> SelectedStateIDs)
{
if (StateTreeViewModel)
{
TArray<TWeakObjectPtr<UStateTreeState>> Selection;
if (const UStateTreeEditorData* StateTreeEditorData = StateTreeViewModel->GetStateTreeEditorData())
{
for (const FGuid& StateID : SelectedStateIDs)
{
if (const UStateTreeState* State = StateTreeEditorData->GetStateByID(StateID))
{
Selection.Add(const_cast<UStateTreeState*>(State));
}
}
}
StateTreeViewModel->SetSelection(Selection);
}
}
void SStateTreeOutliner::HandleVisualThemeChanged(const UStateTree& StateTree)
{
if (StateTreeViewModel
&& StateTreeViewModel->GetStateTree() == &StateTree)
{
CompactStateTreeView->Refresh(StateTreeViewModel->GetStateTreeEditorData());
}
}
FReply SStateTreeOutliner::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent)
{
if(CommandList->ProcessCommandBindings(InKeyEvent))
{
return FReply::Handled();
}
else
{
return SCompoundWidget::OnKeyDown(MyGeometry, InKeyEvent);
}
}
TSharedPtr<SWidget> SStateTreeOutliner::HandleContextMenuOpening()
{
if (!StateTreeViewModel)
{
return nullptr;
}
FMenuBuilder MenuBuilder(true, CommandList);
MenuBuilder.AddSubMenu(
LOCTEXT("AddState", "Add State"),
FText(),
FNewMenuDelegate::CreateLambda([this](FMenuBuilder& MenuBuilder)
{
MenuBuilder.AddMenuEntry(FStateTreeEditorCommands::Get().AddSiblingState);
MenuBuilder.AddMenuEntry(FStateTreeEditorCommands::Get().AddChildState);
}),
/*bInOpenSubMenuOnClick =*/false,
FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Plus")
);
MenuBuilder.AddSeparator();
MenuBuilder.AddMenuEntry(FStateTreeEditorCommands::Get().CutStates);
MenuBuilder.AddMenuEntry(FStateTreeEditorCommands::Get().CopyStates);
MenuBuilder.AddSubMenu(
LOCTEXT("Paste", "Paste"),
FText(),
FNewMenuDelegate::CreateLambda([this](FMenuBuilder& MenuBuilder)
{
MenuBuilder.AddMenuEntry(FStateTreeEditorCommands::Get().PasteStatesAsSiblings);
MenuBuilder.AddMenuEntry(FStateTreeEditorCommands::Get().PasteStatesAsChildren);
}),
/*bInOpenSubMenuOnClick =*/false,
FSlateIcon(FAppStyle::GetAppStyleSetName(), "GenericCommands.Paste")
);
MenuBuilder.AddMenuEntry(FStateTreeEditorCommands::Get().DuplicateStates);
MenuBuilder.AddMenuEntry(FStateTreeEditorCommands::Get().DeleteStates);
MenuBuilder.AddSeparator();
MenuBuilder.AddMenuEntry(FStateTreeEditorCommands::Get().EnableStates);
#if WITH_STATETREE_TRACE_DEBUGGER
MenuBuilder.AddSeparator();
MenuBuilder.AddMenuEntry(FStateTreeEditorCommands::Get().EnableOnEnterStateBreakpoint);
MenuBuilder.AddMenuEntry(FStateTreeEditorCommands::Get().EnableOnExitStateBreakpoint);
#endif // WITH_STATETREE_TRACE_DEBUGGER
return MenuBuilder.MakeWidget();
}
UStateTreeState* SStateTreeOutliner::GetFirstSelectedState() const
{
TArray<UStateTreeState*> SelectedStates;
if (StateTreeViewModel)
{
StateTreeViewModel->GetSelectedStates(SelectedStates);
}
return SelectedStates.IsEmpty() ? nullptr : SelectedStates[0];
}
void SStateTreeOutliner::HandleAddSiblingState()
{
if (StateTreeViewModel)
{
StateTreeViewModel->AddState(GetFirstSelectedState());
}
}
void SStateTreeOutliner::HandleAddChildState()
{
if (StateTreeViewModel)
{
UStateTreeState* ParentState = GetFirstSelectedState();
if (ParentState)
{
StateTreeViewModel->AddChildState(ParentState);
}
}
}
void SStateTreeOutliner::HandleCutSelectedStates()
{
if (StateTreeViewModel)
{
StateTreeViewModel->CopySelectedStates();
StateTreeViewModel->RemoveSelectedStates();
}
}
void SStateTreeOutliner::HandleCopySelectedStates()
{
if (StateTreeViewModel)
{
StateTreeViewModel->CopySelectedStates();
}
}
void SStateTreeOutliner::HandlePasteStatesAsSiblings()
{
if (StateTreeViewModel)
{
StateTreeViewModel->PasteStatesFromClipboard(GetFirstSelectedState());
}
}
void SStateTreeOutliner::HandlePasteStatesAsChildren()
{
if (StateTreeViewModel)
{
StateTreeViewModel->PasteStatesAsChildrenFromClipboard(GetFirstSelectedState());
}
}
void SStateTreeOutliner::HandleDuplicateSelectedStates()
{
if (StateTreeViewModel)
{
StateTreeViewModel->DuplicateSelectedStates();
}
}
void SStateTreeOutliner::HandleDeleteStates()
{
if (StateTreeViewModel)
{
StateTreeViewModel->RemoveSelectedStates();
}
}
void SStateTreeOutliner::HandleEnableSelectedStates()
{
if (StateTreeViewModel)
{
// Process CanEnable first so in case of undetermined state (mixed selection) we Enable by default.
if (CanEnableStates())
{
StateTreeViewModel->SetSelectedStatesEnabled(true);
}
else if (CanDisableStates())
{
StateTreeViewModel->SetSelectedStatesEnabled(false);
}
}
}
void SStateTreeOutliner::HandleDisableSelectedStates()
{
if (StateTreeViewModel)
{
StateTreeViewModel->SetSelectedStatesEnabled(false);
}
}
bool SStateTreeOutliner::HasSelection() const
{
return StateTreeViewModel && StateTreeViewModel->HasSelection();
}
bool SStateTreeOutliner::CanPaste() const
{
return StateTreeViewModel
&& StateTreeViewModel->HasSelection()
&& StateTreeViewModel->CanPasteStatesFromClipboard();
}
bool SStateTreeOutliner::CanEnableStates() const
{
return StateTreeViewModel
&& StateTreeViewModel->HasSelection()
&& StateTreeViewModel->CanEnableStates();
}
bool SStateTreeOutliner::CanDisableStates() const
{
return StateTreeViewModel
&& StateTreeViewModel->HasSelection()
&& StateTreeViewModel->CanDisableStates();
}
#undef LOCTEXT_NAMESPACE