// 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& 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 UToolMenu::GetMenuHierarchyNames(bool bIncludeSubMenuRoot) const { TArray HierarchyNames; TArray 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 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 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 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 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& 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 UToolMenu::GetSubMenuChain() const { TArray SubMenuChain; TSet 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 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(); }