Files
UnrealEngine/Engine/Source/Developer/ToolMenus/Private/ToolMenu.cpp
2025-05-18 13:04:45 +08:00

690 lines
17 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "ToolMenu.h"
#include "ToolMenus.h"
#include "IToolMenusModule.h"
#include "Textures/SlateIcon.h"
#include "Framework/Commands/UIAction.h"
#include "Framework/MultiBox/MultiBox.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "Internationalization/Internationalization.h"
#include "Styling/SlateStyleRegistry.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(ToolMenu)
UToolMenu::UToolMenu() :
MenuType(EMultiBoxType::Menu)
, bShouldCleanupContextOnDestroy(true)
, bShouldCloseWindowAfterMenuSelection(true)
, bCloseSelfOnly(false)
, bSearchable(true)
, bToolBarIsFocusable(false)
, bToolBarForceSmallIcons(false)
, bRegistered(false)
, bIsRegistering(false)
, bExtendersEnabled(true)
, MaxHeight(1000)
{
}
void UToolMenu::InitMenu(const FToolMenuOwner InOwner, FName InName, FName InParent, EMultiBoxType InType)
{
MenuOwner = InOwner;
MenuName = InName;
MenuParent = InParent;
MenuType = InType;
}
FReply UToolMenu::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent)
{
for (int32 i = 0; i < Sections.Num(); ++i)
{
for (FToolMenuEntry& Entry : Sections[i].Blocks)
{
if (Entry.Type == EMultiBlockType::ToolBarButton
&& Entry.IsCommandKeybindOnly())
{
if (Entry.CommandAcceptsInput(InKeyEvent))
{
if (Entry.TryExecuteToolUIAction(Context))
{
return FReply::Handled();
}
}
}
}
}
return FReply::Unhandled();
}
const ISlateStyle* UToolMenu::GetStyleSet() const
{
if (!StyleSetName.IsNone())
{
const ISlateStyle* FoundSlateStyleSet = FSlateStyleRegistry::FindSlateStyle(StyleSetName);
ensureMsgf(FoundSlateStyleSet, TEXT("Slate Style Set not found: '%s' for menu: '%s'."), *StyleSetName.ToString(), *GetMenuName().ToString());
return FoundSlateStyleSet;
}
return &FCoreStyle::Get();
}
void UToolMenu::SetStyleSet(const ISlateStyle* InStyleSet)
{
const FName NewStyleSetName = InStyleSet ? InStyleSet->GetStyleSetName() : NAME_None;
if (NewStyleSetName != StyleName)
{
StyleSetName = NewStyleSetName;
}
}
void UToolMenu::InitGeneratedCopy(const UToolMenu* Source, const FName InMenuName, const FToolMenuContext* InContext)
{
// Skip sections
MenuName = InMenuName;
MenuParent = Source->MenuParent;
StyleName = Source->StyleName;
TutorialHighlightName = Source->TutorialHighlightName;
MenuType = Source->MenuType;
StyleSetName = Source->StyleSetName;
bShouldCloseWindowAfterMenuSelection = Source->bShouldCloseWindowAfterMenuSelection;
bCloseSelfOnly = Source->bCloseSelfOnly;
bSearchable = Source->bSearchable;
bSeparateSections = Source->bSeparateSections;
bToolBarIsFocusable = Source->bToolBarIsFocusable;
bToolBarForceSmallIcons = Source->bToolBarForceSmallIcons;
MenuOwner = Source->MenuOwner;
SubMenuParent = Source->SubMenuParent;
SubMenuSourceEntryName = Source->SubMenuSourceEntryName;
MaxHeight = Source->MaxHeight;
bExtendersEnabled = Source->bExtendersEnabled;
MaxHeight = Source->MaxHeight;
if (InContext)
{
Context = *InContext;
}
}
int32 UToolMenu::IndexOfSection(const FName InSectionName) const
{
for (int32 i=0; i < Sections.Num(); ++i)
{
if (Sections[i].Name == InSectionName)
{
return i;
}
}
return INDEX_NONE;
}
// Note: This function is very similar to FToolMenuSection::FindBlockInsertIndex.
int32 UToolMenu::FindInsertIndex(const FToolMenuSection& InSection) const
{
const FToolMenuInsert InInsertPosition = InSection.InsertPosition;
// Insert a Default-positioned section after all First and Default-positioned sections but before any
// Last-positioned sections.
if (InInsertPosition.IsDefault())
{
for (int32 i = 0; i < Sections.Num(); ++i)
{
if (Sections[i].InsertPosition.Position == EToolMenuInsertType::Last)
{
return i;
}
}
return Sections.Num();
}
// Insert a First-positioned section after any other First-positioned sections but before all Default and
// Last-positioned sections.
if (InInsertPosition.Position == EToolMenuInsertType::First)
{
for (int32 i = 0; i < Sections.Num(); ++i)
{
if (Sections[i].InsertPosition.Position != InInsertPosition.Position)
{
return i;
}
}
return Sections.Num();
}
// Insert a Last-positioned section after all other sections, include other Last-positioned sections.
if (InInsertPosition.Position == EToolMenuInsertType::Last)
{
for (int32 i = Sections.Num() - 1; i >= 0; --i)
{
if (Sections[i].InsertPosition.Position == InInsertPosition.Position)
{
return i + 1;
}
}
return Sections.Num();
}
int32 DestIndex = IndexOfSection(InInsertPosition.Name);
if (DestIndex == INDEX_NONE)
{
return DestIndex;
}
if (InInsertPosition.Position == EToolMenuInsertType::After)
{
++DestIndex;
// Insert after the final entry that has the exact same InsertPosition
for (int32 i = DestIndex; i < Sections.Num(); ++i)
{
if (Sections[i].InsertPosition == InInsertPosition)
{
DestIndex = i + 1;
// Do not break because EToolMenuInsertType::Before may have been used
}
}
}
for (int32 i = DestIndex; i < Sections.Num(); ++i)
{
if (Sections[i].InsertPosition != InInsertPosition)
{
return i;
}
}
return Sections.Num();
}
FToolMenuSection& UToolMenu::AddDynamicSection(const FName SectionName, const FNewSectionConstructChoice& InConstruct, const FToolMenuInsert InPosition)
{
FToolMenuSection& Section = AddSection(SectionName, TAttribute< FText >(), InPosition);
Section.Construct = InConstruct;
return Section;
}
bool UToolMenu::IsRegistering() const
{
return bIsRegistering;
}
FToolMenuSection& UToolMenu::AddSection(const FName SectionName, const TAttribute< FText >& InLabel, const FToolMenuInsert InPosition)
{
int32 InsertIndex = (SectionName != NAME_None) ? IndexOfSection(SectionName) : INDEX_NONE;
if (InsertIndex != INDEX_NONE)
{
if (InLabel.IsSet())
{
Sections[InsertIndex].Label = InLabel;
}
if (InPosition.Name != NAME_None || InPosition.Position != EToolMenuInsertType::Default)
{
Sections[InsertIndex].InsertPosition = InPosition;
}
// Sort registered sections to appear before unregistered
if (IsRegistering() && !Sections[InsertIndex].bAddedDuringRegister)
{
Sections[InsertIndex].bAddedDuringRegister = true;
for (int32 i = 0; i < InsertIndex; ++i)
{
if (!Sections[i].bAddedDuringRegister)
{
FToolMenuSection RemovedSection;
Swap(Sections[InsertIndex], RemovedSection);
Sections.Insert(MoveTempIfPossible(RemovedSection), i);
Sections.RemoveAt(InsertIndex + 1, EAllowShrinking::No);
InsertIndex = i;
}
}
}
return Sections[InsertIndex];
}
else
{
InsertIndex = Sections.Num();
}
if (IsRegistering())
{
for (int32 i=0; i < Sections.Num(); ++i)
{
if (!Sections[i].bAddedDuringRegister)
{
InsertIndex = i;
break;
}
}
}
FToolMenuSection& NewSection = Sections.InsertDefaulted_GetRef(InsertIndex);
NewSection.InitSection(SectionName, InLabel, InPosition);
NewSection.Owner = UToolMenus::Get()->CurrentOwner();
NewSection.bIsRegistering = IsRegistering();
NewSection.bAddedDuringRegister = IsRegistering();
return NewSection;
}
void UToolMenu::AddSectionScript(
const FName SectionName,
const FText& InLabel,
const FName InsertName,
const EToolMenuInsertType InsertType,
EToolMenuSectionAlign Alignment
)
{
FToolMenuSection& Section = FindOrAddSection(SectionName);
if (!InLabel.IsEmpty())
{
Section.Label = InLabel;
}
if (InsertName != NAME_None || InsertType != EToolMenuInsertType::Default)
{
Section.InsertPosition = FToolMenuInsert(InsertName, InsertType);
}
Section.Alignment = Alignment;
}
void UToolMenu::AddDynamicSectionScript(const FName SectionName, UToolMenuSectionDynamic* InObject)
{
FToolMenuSection& Section = FindOrAddSection(SectionName);
Section.ToolMenuSectionDynamic = InObject;
}
void UToolMenu::AddMenuEntryObject(UToolMenuEntryScript* InObject)
{
FindOrAddSection(InObject->Data.Section).AddEntryObject(InObject);
if (MenuType == EMultiBoxType::MenuBar || MenuType == EMultiBoxType::ToolBar)
{
UToolMenus::Get()->RefreshAllWidgets();
}
}
void UToolMenu::RemoveMenuEntryObject(UToolMenuEntryScript* InObject)
{
if (FToolMenuSection* Section = FindSection(InObject->Data.Section))
{
Section->RemoveEntryObject(InObject);
}
if (MenuType == EMultiBoxType::MenuBar || MenuType == EMultiBoxType::ToolBar)
{
UToolMenus::Get()->RefreshAllWidgets();
}
}
UToolMenu* UToolMenu::AddSubMenuScript(const FName InOwner, const FName SectionName, const FName InName, const FText& InLabel, const FText& InToolTip)
{
return AddSubMenu(InOwner, SectionName, InName, InLabel, InToolTip);
}
UToolMenu* UToolMenu::AddSubMenu(const FToolMenuOwner InOwner, const FName SectionName, const FName InName, const FText& InLabel, const FText& InToolTip)
{
FToolMenuEntry Args = FToolMenuEntry::InitSubMenu(InName, InLabel, InToolTip, FNewToolMenuChoice());
Args.Owner = InOwner;
FindOrAddSection(SectionName).AddEntry(Args);
return UToolMenus::Get()->ExtendMenu(*(MenuName.ToString() + TEXT(".") + InName.ToString()));
}
FToolMenuSection* UToolMenu::FindSection(const FName SectionName)
{
for (FToolMenuSection& Section : Sections)
{
if (Section.Name == SectionName)
{
return &Section;
}
}
return nullptr;
}
FToolMenuSection& UToolMenu::FindOrAddSection(const FName SectionName)
{
for (FToolMenuSection& Section : Sections)
{
if (Section.Name == SectionName)
{
return Section;
}
}
return AddSection(SectionName);
}
FToolMenuSection& UToolMenu::FindOrAddSection(
const FName SectionName,
const TAttribute<FText>& InLabel,
const FToolMenuInsert InPosition)
{
if (FToolMenuSection* FoundSection = FindSection(SectionName))
{
return *FoundSection;
}
return AddSection(SectionName, InLabel, InPosition);
}
void UToolMenu::RemoveSection(const FName SectionName)
{
Sections.RemoveAll([SectionName](const FToolMenuSection& Section) { return Section.Name == SectionName; });
}
bool UToolMenu::FindEntry(const FName EntryName, int32& SectionIndex, int32& EntryIndex) const
{
for (int32 i=0; i < Sections.Num(); ++i)
{
EntryIndex = Sections[i].IndexOfBlock(EntryName);
if (EntryIndex != INDEX_NONE)
{
SectionIndex = i;
return true;
}
}
return false;
}
const FToolMenuEntry* UToolMenu::FindEntry(const FName EntryName) const
{
for (int32 i=0; i < Sections.Num(); ++i)
{
if (const FToolMenuEntry* Found = Sections[i].FindEntry(EntryName))
{
return Found;
}
}
return nullptr;
}
FToolMenuEntry* UToolMenu::FindEntry(const FName EntryName)
{
for (int32 i=0; i < Sections.Num(); ++i)
{
if (FToolMenuEntry* Found = Sections[i].FindEntry(EntryName))
{
return Found;
}
}
return nullptr;
}
void UToolMenu::AddMenuEntry(const FName SectionName, const FToolMenuEntry& Args)
{
FindOrAddSection(SectionName).AddEntry(Args);
}
bool UToolMenu::IsEditing() const
{
return Context.IsEditing();
}
FName UToolMenu::GetSectionName(const FName InEntryName) const
{
for (const FToolMenuSection& Section : Sections)
{
if (Section.IndexOfBlock(InEntryName) != INDEX_NONE)
{
return Section.Name;
}
}
return NAME_None;
}
bool UToolMenu::ContainsSection(const FName InName) const
{
if (InName != NAME_None)
{
for (const FToolMenuSection& Section : Sections)
{
if (Section.Name == InName)
{
return true;
}
}
}
return false;
}
bool UToolMenu::ContainsEntry(const FName InName) const
{
if (InName != NAME_None)
{
for (const FToolMenuSection& Section : Sections)
{
if (Section.FindEntry(InName) != nullptr)
{
return true;
}
}
}
return false;
}
FCustomizedToolMenu* UToolMenu::FindMenuCustomization() const
{
return UToolMenus::Get()->FindMenuCustomization(MenuName);
}
FCustomizedToolMenu* UToolMenu::AddMenuCustomization() const
{
return UToolMenus::Get()->AddMenuCustomization(MenuName);
}
TArray<FName> UToolMenu::GetMenuHierarchyNames(bool bIncludeSubMenuRoot) const
{
TArray<FName> HierarchyNames;
TArray<UToolMenu*> Hierarchy;
if (UToolMenus::Get()->FindMenu(GetMenuName()) != nullptr)
{
Hierarchy = UToolMenus::Get()->CollectHierarchy(GetMenuName());
for (int32 i = Hierarchy.Num() - 1; i >= 0; --i)
{
HierarchyNames.AddUnique(Hierarchy[i]->GetMenuName());
}
}
if (bIncludeSubMenuRoot && SubMenuParent)
{
TArray<const UToolMenu*> SubMenuChain = GetSubMenuChain();
if (SubMenuChain.Num() > 0)
{
FString SubMenuFullPath;
for (int32 i = 1; i < SubMenuChain.Num(); ++i)
{
if (SubMenuFullPath.Len() > 0)
{
SubMenuFullPath += TEXT(".");
}
SubMenuFullPath += SubMenuChain[i]->SubMenuSourceEntryName.ToString();
}
// Hierarchy of the initial menu opened in the sub-menu chain of menus
TArray<UToolMenu*> FirstMenuHierarchy = UToolMenus::Get()->CollectHierarchy(SubMenuChain[0]->GetMenuName());
for (int32 i = FirstMenuHierarchy.Num() - 1; i >= 0; --i)
{
HierarchyNames.AddUnique(UToolMenus::JoinMenuPaths(FirstMenuHierarchy[i]->GetMenuName(), *SubMenuFullPath));
}
}
}
Algo::Reverse(HierarchyNames);
return HierarchyNames;
}
FCustomizedToolMenuHierarchy UToolMenu::GetMenuCustomizationHierarchy() const
{
FCustomizedToolMenuHierarchy Result;
UToolMenus* ToolMenus = UToolMenus::Get();
TArray<FName> HierarchyNames = GetMenuHierarchyNames(true);
for (const FName& ItName : HierarchyNames)
{
if (FCustomizedToolMenu* Found = ToolMenus->FindMenuCustomization(ItName))
{
Result.Hierarchy.Add(Found);
}
if (FCustomizedToolMenu* FoundRuntime = ToolMenus->FindRuntimeMenuCustomization(ItName))
{
Result.RuntimeHierarchy.Add(FoundRuntime);
}
}
return Result;
}
FToolMenuProfile* UToolMenu::FindMenuProfile(const FName& ProfileName) const
{
return UToolMenus::Get()->FindMenuProfile(MenuName, ProfileName);
}
FToolMenuProfile* UToolMenu::AddMenuProfile(const FName& ProfileName) const
{
return UToolMenus::Get()->AddMenuProfile(MenuName, ProfileName);
}
FToolMenuProfileHierarchy UToolMenu::GetMenuProfileHierarchy(const FName& ProfileName) const
{
FToolMenuProfileHierarchy Result;
UToolMenus* ToolMenus = UToolMenus::Get();
TArray<FName> HierarchyNames = GetMenuHierarchyNames(true);
for (const FName& ItName : HierarchyNames)
{
if (FToolMenuProfile* Found = ToolMenus->FindMenuProfile(ItName, ProfileName))
{
Result.ProfileHierarchy.Add(Found);
}
if (FToolMenuProfile* FoundRuntime = ToolMenus->FindRuntimeMenuProfile(ItName, ProfileName))
{
Result.RuntimeProfileHierarchy.Add(FoundRuntime);
}
}
return Result;
}
void UToolMenu::UpdateMenuCustomizationFromMultibox(const TSharedRef<const FMultiBox>& InMultiBox)
{
FCustomizedToolMenu* Customization = AddMenuCustomization();
Customization->EntryOrder.Reset();
Customization->SectionOrder.Reset();
FName CurrentSectionName = NAME_None;
const TArray< TSharedRef< const FMultiBlock > >& Blocks = InMultiBox->GetBlocks();
for (int32 BlockIndex = 0; BlockIndex < Blocks.Num(); ++BlockIndex)
{
const TSharedRef< const FMultiBlock >& Block = Blocks[BlockIndex];
if (Block->GetExtensionHook() == NAME_None)
{
continue;
}
// Ignore separators that are part of a section heading
if (Block->IsSeparator() && (BlockIndex + 1 < Blocks.Num()) && Blocks[BlockIndex + 1]->GetType() == EMultiBlockType::Heading)
{
continue;
}
if (Block->GetType() == EMultiBlockType::Heading)
{
CurrentSectionName = Block->GetExtensionHook();
Customization->SectionOrder.Add(CurrentSectionName);
}
else if (CurrentSectionName != NAME_None)
{
FCustomizedToolMenuNameArray& EntryOrderForSection = Customization->EntryOrder.FindOrAdd(CurrentSectionName);
EntryOrderForSection.Names.Add(Block->GetExtensionHook());
}
}
}
void UToolMenu::OnMenuDestroyed()
{
if (bShouldCleanupContextOnDestroy && !SubMenuParent)
{
Context.CleanupObjects();
}
//Empty();
}
TArray<const UToolMenu*> UToolMenu::GetSubMenuChain() const
{
TArray<const UToolMenu*> SubMenuChain;
TSet<const UToolMenu*> SubMenus;
for (const UToolMenu* CurrentMenu = this; CurrentMenu; CurrentMenu = CurrentMenu->SubMenuParent)
{
bool bIsAlreadyInSet = false;
SubMenus.Add(CurrentMenu, &bIsAlreadyInSet);
if (bIsAlreadyInSet)
{
ensure(!bIsAlreadyInSet);
break;
}
SubMenuChain.Add(CurrentMenu);
}
Algo::Reverse(SubMenuChain);
return SubMenuChain;
}
FString UToolMenu::GetSubMenuNamePath() const
{
FString SubMenuNamePath;
TArray<const UToolMenu*> SubMenuChain = GetSubMenuChain();
if (SubMenuChain.Num() > 0)
{
for (int32 i = 1; i < SubMenuChain.Num(); ++i)
{
if (SubMenuNamePath.Len() > 0)
{
SubMenuNamePath += TEXT(".");
}
SubMenuNamePath += SubMenuChain[i]->SubMenuSourceEntryName.ToString();
}
}
return SubMenuNamePath;
}
void UToolMenu::SetExtendersEnabled(bool bEnabled)
{
bExtendersEnabled = bEnabled;
}
void UToolMenu::Empty()
{
Context.Empty();
Sections.Empty();
SubMenuParent = nullptr;
ModifyBlockWidgetAfterMake.Unbind();
}