You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
1586 lines
45 KiB
C++
1586 lines
45 KiB
C++
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#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 "Framework/Application/SlateApplication.h"
|
|
#include "Internationalization/Internationalization.h"
|
|
|
|
#include "HAL/PlatformApplicationMisc.h" // For clipboard
|
|
|
|
#define LOCTEXT_NAMESPACE "ToolMenuSubsystem"
|
|
|
|
UToolMenus* UToolMenus::Singleton = nullptr;
|
|
bool UToolMenus::bHasShutDown = false;
|
|
|
|
FAutoConsoleCommand ToolMenusRefreshMenuWidget = FAutoConsoleCommand(
|
|
TEXT("ToolMenus.RefreshAllWidgets"),
|
|
TEXT("Refresh All Tool Menu Widgets"),
|
|
FConsoleCommandDelegate::CreateLambda([]() {
|
|
UToolMenus::Get()->RefreshAllWidgets();
|
|
}));
|
|
|
|
FName FToolMenuStringCommand::GetTypeName() const
|
|
{
|
|
static const FName CommandName("Command");
|
|
static const FName PythonName("Python");
|
|
|
|
switch (Type)
|
|
{
|
|
case EToolMenuStringCommandType::Command:
|
|
return CommandName;
|
|
case EToolMenuStringCommandType::Python:
|
|
return PythonName;
|
|
case EToolMenuStringCommandType::Custom:
|
|
return CustomType;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return NAME_None;
|
|
}
|
|
|
|
FExecuteAction FToolMenuStringCommand::ToExecuteAction(const FToolMenuContext& Context) const
|
|
{
|
|
if (IsBound())
|
|
{
|
|
return FExecuteAction::CreateStatic(&UToolMenus::ExecuteStringCommand, *this, Context);
|
|
}
|
|
|
|
return FExecuteAction();
|
|
}
|
|
|
|
FToolUIActionChoice::FToolUIActionChoice(const TSharedPtr< const FUICommandInfo >& InCommand, const FUICommandList& InCommandList)
|
|
{
|
|
if (InCommand.IsValid())
|
|
{
|
|
if (const FUIAction* UIAction = InCommandList.GetActionForCommand(InCommand))
|
|
{
|
|
Action = *UIAction;
|
|
ToolAction.Reset();
|
|
DynamicToolAction.Reset();
|
|
}
|
|
}
|
|
}
|
|
|
|
UToolMenus::UToolMenus() :
|
|
bNextTickTimerIsSet(false),
|
|
bRefreshWidgetsNextTick(false),
|
|
bCleanupStaleWidgetsNextTick(false)
|
|
{
|
|
}
|
|
|
|
UToolMenus* UToolMenus::Get()
|
|
{
|
|
if (!Singleton && !bHasShutDown)
|
|
{
|
|
// Required for StartupModule and ShutdownModule to be called and FindModule to list the ToolsMenus module
|
|
FModuleManager::LoadModuleChecked<IToolMenusModule>("ToolMenus");
|
|
|
|
Singleton = NewObject<UToolMenus>();
|
|
Singleton->AddToRoot();
|
|
check(Singleton);
|
|
}
|
|
|
|
return Singleton;
|
|
}
|
|
|
|
void UToolMenus::BeginDestroy()
|
|
{
|
|
if (Singleton == this)
|
|
{
|
|
bHasShutDown = true;
|
|
Singleton = nullptr;
|
|
}
|
|
|
|
Super::BeginDestroy();
|
|
}
|
|
|
|
bool UToolMenus::IsToolMenuUIEnabled()
|
|
{
|
|
if (!FSlateApplication::IsInitialized())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return !IsRunningCommandlet() && !IsRunningGame() && !IsRunningDedicatedServer() && !IsRunningClientOnly();
|
|
}
|
|
|
|
FName UToolMenus::JoinMenuPaths(const FName Base, const FName Child)
|
|
{
|
|
return *(Base.ToString() + TEXT(".") + Child.ToString());
|
|
}
|
|
|
|
bool UToolMenus::GetDisplayUIExtensionPoints() const
|
|
{
|
|
return ShouldDisplayExtensionPoints.IsBound() && ShouldDisplayExtensionPoints.Execute();
|
|
}
|
|
|
|
UToolMenu* UToolMenus::FindMenu(const FName Name)
|
|
{
|
|
UToolMenu** Found = Menus.Find(Name);
|
|
return Found ? *Found : nullptr;
|
|
}
|
|
|
|
bool UToolMenus::IsMenuRegistered(const FName Name) const
|
|
{
|
|
const UToolMenu* const * Found = Menus.Find(Name);
|
|
return Found && *Found && (*Found)->IsRegistered();
|
|
}
|
|
|
|
TArray<UToolMenu*> UToolMenus::CollectHierarchy(const FName InName)
|
|
{
|
|
TArray<UToolMenu*> Result;
|
|
|
|
UToolMenu* Current = FindMenu(InName);
|
|
while (Current)
|
|
{
|
|
// Detect infinite loop
|
|
for (UToolMenu* Other : Result)
|
|
{
|
|
if (Other->MenuName == Current->MenuName)
|
|
{
|
|
UE_LOG(LogToolMenus, Warning, TEXT("Infinite loop detected in tool menu: %s"), *InName.ToString());
|
|
return TArray<UToolMenu*>();
|
|
}
|
|
}
|
|
|
|
Result.Add(Current);
|
|
|
|
if (Current->MenuParent != NAME_None)
|
|
{
|
|
Current = FindMenu(Current->MenuParent);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
Algo::Reverse(Result);
|
|
|
|
return Result;
|
|
}
|
|
|
|
void UToolMenus::ListAllParents(const FName InName, TArray<FName>& AllParents)
|
|
{
|
|
for (const UToolMenu* Menu : CollectHierarchy(InName))
|
|
{
|
|
AllParents.Add(Menu->MenuName);
|
|
}
|
|
}
|
|
|
|
void UToolMenus::AssembleMenuSection(UToolMenu* GeneratedMenu, const UToolMenu* Other, FToolMenuSection* DestSection, const FToolMenuSection& OtherSection)
|
|
{
|
|
// Build list of blocks in expected order including blocks created by construct delegates
|
|
TArray<FToolMenuEntry> RemainingBlocks;
|
|
|
|
UToolMenu* ConstructedEntries = nullptr;
|
|
for (const FToolMenuEntry& Block : OtherSection.Blocks)
|
|
{
|
|
if (!Block.IsNonLegacyDynamicConstruct())
|
|
{
|
|
RemainingBlocks.Add(Block);
|
|
continue;
|
|
}
|
|
|
|
if (ConstructedEntries == nullptr)
|
|
{
|
|
ConstructedEntries = NewObject<UToolMenu>(this);
|
|
ConstructedEntries->Context = DestSection->Context;
|
|
}
|
|
|
|
TArray<FToolMenuEntry> GeneratedEntries;
|
|
GeneratedEntries.Add(Block);
|
|
|
|
int32 NumIterations = 0;
|
|
while (GeneratedEntries.Num() > 0)
|
|
{
|
|
FToolMenuEntry& GeneratedEntry = GeneratedEntries[0];
|
|
if (GeneratedEntry.IsNonLegacyDynamicConstruct())
|
|
{
|
|
if (NumIterations++ > 5000)
|
|
{
|
|
UE_LOG(LogToolMenus, Warning, TEXT("Possible infinite loop for menu: %s, section: %s, block: %s"), *Other->MenuName.ToString(), *OtherSection.Name.ToString(), *Block.Name.ToString());
|
|
break;
|
|
}
|
|
|
|
ConstructedEntries->Sections.Reset();
|
|
if (GeneratedEntry.IsScriptObjectDynamicConstruct())
|
|
{
|
|
GeneratedEntry.ScriptObject->ConstructMenuEntry(ConstructedEntries, DestSection->Name, DestSection->Context);
|
|
}
|
|
else
|
|
{
|
|
FToolMenuSection& ConstructedSection = ConstructedEntries->AddSection(DestSection->Name);
|
|
ConstructedSection.Context = ConstructedEntries->Context;
|
|
GeneratedEntry.Construct.Execute(ConstructedSection);
|
|
}
|
|
GeneratedEntries.RemoveAt(0, 1, false);
|
|
|
|
// Combine all user's choice of selections here into the current section target
|
|
// If the user wants to add items to different sections they will need to create dynamic section instead (for now)
|
|
int32 NumBlocksInserted = 0;
|
|
for (FToolMenuSection& ConstructedSection : ConstructedEntries->Sections)
|
|
{
|
|
for (FToolMenuEntry& ConstructedBlock : ConstructedSection.Blocks)
|
|
{
|
|
if (ConstructedBlock.InsertPosition.IsDefault())
|
|
{
|
|
ConstructedBlock.InsertPosition = Block.InsertPosition;
|
|
}
|
|
}
|
|
GeneratedEntries.Insert(ConstructedSection.Blocks, NumBlocksInserted);
|
|
NumBlocksInserted += ConstructedSection.Blocks.Num();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RemainingBlocks.Add(GeneratedEntry);
|
|
GeneratedEntries.RemoveAt(0, 1, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ConstructedEntries)
|
|
{
|
|
ConstructedEntries->Sections.Empty();
|
|
ConstructedEntries = nullptr;
|
|
}
|
|
|
|
// Repeatedly loop because insert location may not exist until later in list
|
|
while (RemainingBlocks.Num() > 0)
|
|
{
|
|
int32 NumHandled = 0;
|
|
for (int32 i = 0; i < RemainingBlocks.Num(); ++i)
|
|
{
|
|
FToolMenuEntry& Block = RemainingBlocks[i];
|
|
int32 DestIndex = DestSection->FindBlockInsertIndex(Block);
|
|
if (DestIndex != INDEX_NONE)
|
|
{
|
|
DestSection->Blocks.Insert(Block, DestIndex);
|
|
RemainingBlocks.RemoveAt(i);
|
|
--i;
|
|
++NumHandled;
|
|
// Restart loop because items earlier in the list may need to attach to this block
|
|
break;
|
|
}
|
|
}
|
|
if (NumHandled == 0)
|
|
{
|
|
for (const FToolMenuEntry& Block : RemainingBlocks)
|
|
{
|
|
UE_LOG(LogToolMenus, Warning, TEXT("Menu item not found: '%s' for insert: '%s'"), *Block.InsertPosition.Name.ToString(), *Block.Name.ToString());
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void UToolMenus::AssembleMenu(UToolMenu* GeneratedMenu, const UToolMenu* Other)
|
|
{
|
|
TArray<FToolMenuSection> RemainingSections;
|
|
|
|
UToolMenu* ConstructedSections = nullptr;
|
|
for (const FToolMenuSection& OtherSection : Other->Sections)
|
|
{
|
|
if (!OtherSection.IsNonLegacyDynamic())
|
|
{
|
|
RemainingSections.Add(OtherSection);
|
|
continue;
|
|
}
|
|
|
|
if (ConstructedSections == nullptr)
|
|
{
|
|
ConstructedSections = NewObject<UToolMenu>(this);
|
|
ConstructedSections->Context = GeneratedMenu->Context;
|
|
}
|
|
|
|
TArray<FToolMenuSection> GeneratedSections;
|
|
GeneratedSections.Add(OtherSection);
|
|
|
|
int32 NumIterations = 0;
|
|
while (GeneratedSections.Num() > 0)
|
|
{
|
|
if (GeneratedSections[0].IsNonLegacyDynamic())
|
|
{
|
|
if (NumIterations++ > 5000)
|
|
{
|
|
UE_LOG(LogToolMenus, Warning, TEXT("Possible infinite loop for menu: %s, section: %s"), *Other->MenuName.ToString(), *OtherSection.Name.ToString());
|
|
break;
|
|
}
|
|
|
|
ConstructedSections->Sections.Reset();
|
|
|
|
if (GeneratedSections[0].ToolMenuSectionDynamic)
|
|
{
|
|
GeneratedSections[0].ToolMenuSectionDynamic->ConstructSections(ConstructedSections, GeneratedMenu->Context);
|
|
}
|
|
else if (GeneratedSections[0].Construct.NewToolMenuDelegate.IsBound())
|
|
{
|
|
GeneratedSections[0].Construct.NewToolMenuDelegate.Execute(ConstructedSections);
|
|
}
|
|
|
|
for (FToolMenuSection& ConstructedSection : ConstructedSections->Sections)
|
|
{
|
|
if (ConstructedSection.InsertPosition.IsDefault())
|
|
{
|
|
ConstructedSection.InsertPosition = GeneratedSections[0].InsertPosition;
|
|
}
|
|
}
|
|
|
|
GeneratedSections.RemoveAt(0, 1, false);
|
|
GeneratedSections.Insert(ConstructedSections->Sections, 0);
|
|
}
|
|
else
|
|
{
|
|
RemainingSections.Add(GeneratedSections[0]);
|
|
GeneratedSections.RemoveAt(0, 1, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ConstructedSections)
|
|
{
|
|
ConstructedSections->Sections.Empty();
|
|
ConstructedSections = nullptr;
|
|
}
|
|
|
|
while (RemainingSections.Num() > 0)
|
|
{
|
|
int32 NumHandled = 0;
|
|
for (int32 i=0; i < RemainingSections.Num(); ++i)
|
|
{
|
|
const FToolMenuSection& RemainingSection = RemainingSections[i];
|
|
|
|
// Update existing section
|
|
FToolMenuSection* Section = GeneratedMenu->FindSection(RemainingSection.Name);
|
|
if (!Section)
|
|
{
|
|
// Try add new section (if insert location exists)
|
|
int32 DestIndex = GeneratedMenu->FindInsertIndex(RemainingSection);
|
|
if (DestIndex != INDEX_NONE)
|
|
{
|
|
GeneratedMenu->Sections.InsertDefaulted(DestIndex);
|
|
Section = &GeneratedMenu->Sections[DestIndex];
|
|
Section->InitGeneratedSectionCopy(RemainingSection, GeneratedMenu->Context);
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Allow overriding label
|
|
if (!Section->Label.IsSet() && RemainingSection.Label.IsSet())
|
|
{
|
|
Section->Label = RemainingSection.Label;
|
|
}
|
|
}
|
|
|
|
AssembleMenuSection(GeneratedMenu, Other, Section, RemainingSection);
|
|
RemainingSections.RemoveAt(i);
|
|
--i;
|
|
++NumHandled;
|
|
break;
|
|
}
|
|
if (NumHandled == 0)
|
|
{
|
|
for (const FToolMenuSection& RemainingSection : RemainingSections)
|
|
{
|
|
UE_LOG(LogToolMenus, Warning, TEXT("Menu section not found: '%s' for insert: '%s'"), *RemainingSection.InsertPosition.Name.ToString(), *RemainingSection.Name.ToString());
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void UToolMenus::RemoveCustomization(const FName InName)
|
|
{
|
|
int32 FoundIndex = FindMenuCustomizationIndex(InName);
|
|
if (FoundIndex != INDEX_NONE)
|
|
{
|
|
CustomizedMenus.RemoveAt(FoundIndex, 1, false);
|
|
}
|
|
}
|
|
|
|
int32 UToolMenus::FindMenuCustomizationIndex(const FName InName)
|
|
{
|
|
for (int32 i = 0; i < CustomizedMenus.Num(); ++i)
|
|
{
|
|
if (CustomizedMenus[i].Name == InName)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return INDEX_NONE;
|
|
}
|
|
|
|
FCustomizedToolMenu* UToolMenus::FindMenuCustomization(const FName InName)
|
|
{
|
|
for (int32 i = 0; i < CustomizedMenus.Num(); ++i)
|
|
{
|
|
if (CustomizedMenus[i].Name == InName)
|
|
{
|
|
return &CustomizedMenus[i];
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
FCustomizedToolMenu* UToolMenus::AddMenuCustomization(const FName InName)
|
|
{
|
|
if (FCustomizedToolMenu* Found = FindMenuCustomization(InName))
|
|
{
|
|
return Found;
|
|
}
|
|
else
|
|
{
|
|
FCustomizedToolMenu& NewCustomization = CustomizedMenus.AddDefaulted_GetRef();
|
|
NewCustomization.Name = InName;
|
|
return &NewCustomization;
|
|
}
|
|
}
|
|
|
|
void UToolMenus::ApplyCustomization(UToolMenu* GeneratedMenu)
|
|
{
|
|
FCustomizedToolMenu* CustomizedMenu = FindMenuCustomization(GeneratedMenu->MenuName);
|
|
if (!CustomizedMenu)
|
|
{
|
|
return;
|
|
}
|
|
|
|
TArray<FToolMenuSection> NewSections;
|
|
NewSections.Reserve(GeneratedMenu->Sections.Num());
|
|
|
|
for (const FCustomizedToolMenuSection& CustomizedSection : CustomizedMenu->Sections)
|
|
{
|
|
int32 SectionIndex = GeneratedMenu->IndexOfSection(CustomizedSection.Name);
|
|
if (SectionIndex == INDEX_NONE)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FToolMenuSection& Section = GeneratedMenu->Sections[SectionIndex];
|
|
|
|
TArray<FToolMenuEntry> NewBlocks;
|
|
NewBlocks.Reserve(Section.Blocks.Num());
|
|
|
|
for (const FCustomizedToolMenuEntry& Entry : CustomizedSection.Entries)
|
|
{
|
|
int32 EntrySectionIndex = INDEX_NONE;
|
|
int32 EntryIndex = INDEX_NONE;
|
|
if (GeneratedMenu->FindEntry(Entry.Name, EntrySectionIndex, EntryIndex))
|
|
{
|
|
NewBlocks.Add(GeneratedMenu->Sections[EntrySectionIndex].Blocks[EntryIndex]);
|
|
GeneratedMenu->Sections[EntrySectionIndex].Blocks.RemoveAt(EntryIndex);
|
|
}
|
|
}
|
|
|
|
// Remaining blocks
|
|
for (FToolMenuEntry& Block : Section.Blocks)
|
|
{
|
|
NewBlocks.Add(Block);
|
|
}
|
|
|
|
Section.Blocks = NewBlocks;
|
|
|
|
NewSections.Add(Section);
|
|
GeneratedMenu->Sections.RemoveAt(SectionIndex);
|
|
}
|
|
|
|
// Remaining sections
|
|
NewSections.Append(GeneratedMenu->Sections);
|
|
|
|
// Hide
|
|
if (!GeneratedMenu->IsEditing())
|
|
{
|
|
for (int32 SectionIndex = 0; SectionIndex < NewSections.Num(); ++SectionIndex)
|
|
{
|
|
FToolMenuSection& Section = NewSections[SectionIndex];
|
|
if (CustomizedMenu->HiddenSections.Contains(Section.Name))
|
|
{
|
|
NewSections.RemoveAt(SectionIndex);
|
|
--SectionIndex;
|
|
continue;
|
|
}
|
|
|
|
for (int32 i = 0; i < Section.Blocks.Num(); ++i)
|
|
{
|
|
if (CustomizedMenu->HiddenEntries.Contains(Section.Blocks[i].Name))
|
|
{
|
|
Section.Blocks.RemoveAt(i);
|
|
--i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
GeneratedMenu->Sections = NewSections;
|
|
}
|
|
|
|
void UToolMenus::AssembleMenuHierarchy(UToolMenu* GeneratedMenu, const TArray<UToolMenu*>& Hierarchy)
|
|
{
|
|
if (GeneratedMenu->MenuType == EMultiBoxType::MenuBar)
|
|
{
|
|
// Menu Bars require one section
|
|
if (GeneratedMenu->Sections.Num() == 0)
|
|
{
|
|
GeneratedMenu->Sections.AddDefaulted();
|
|
}
|
|
|
|
FToolMenuSection& MenuBarSection = GeneratedMenu->Sections[0];
|
|
for (const UToolMenu* MenuData : Hierarchy)
|
|
{
|
|
for (const FToolMenuSection& Section : MenuData->Sections)
|
|
{
|
|
for (const FToolMenuEntry& Block : Section.Blocks)
|
|
{
|
|
MenuBarSection.AssembleBlock(Block);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (const UToolMenu* FoundParent : Hierarchy)
|
|
{
|
|
AssembleMenu(GeneratedMenu, FoundParent);
|
|
}
|
|
}
|
|
|
|
ApplyCustomization(GeneratedMenu);
|
|
}
|
|
|
|
void UToolMenus::FillMenuDynamic(FMenuBuilder& MenuBuilder, const FName SubMenuFullName, FNewToolMenuDelegate InConstructMenu, const FToolMenuContext Context)
|
|
{
|
|
TArray<UToolMenu*> Hierarchy;
|
|
|
|
if (SubMenuFullName != NAME_None)
|
|
{
|
|
Hierarchy = CollectHierarchy(SubMenuFullName);
|
|
}
|
|
|
|
// Construct menu using delegate and insert as root so it can be overridden
|
|
if (InConstructMenu.IsBound())
|
|
{
|
|
UToolMenu* Menu = NewObject<UToolMenu>(this);
|
|
Menu->Context = Context;
|
|
Menu->MenuName = SubMenuFullName;
|
|
InConstructMenu.Execute(Menu);
|
|
Menu->MenuName = SubMenuFullName;
|
|
Hierarchy.Insert(Menu, 0);
|
|
}
|
|
|
|
// Populate menu builder with final menu
|
|
UToolMenu* GeneratedMenu = GenerateMenu(Hierarchy, Context);
|
|
PopulateMenuBuilder(MenuBuilder, GeneratedMenu);
|
|
}
|
|
|
|
void UToolMenus::FillMenu(class FMenuBuilder& MenuBuilder, FName InMenuName, FToolMenuContext InMenuContext)
|
|
{
|
|
// Populate menu builder with final menu
|
|
PopulateMenuBuilder(MenuBuilder, GenerateMenu(InMenuName, InMenuContext));
|
|
}
|
|
|
|
TSharedRef<SWidget> UToolMenus::GenerateToolbarComboButtonMenu(const FName SubMenuFullName, FToolMenuContext InContext)
|
|
{
|
|
return GenerateWidget(SubMenuFullName, InContext);
|
|
}
|
|
|
|
void UToolMenus::FillMenuBarDropDown(class FMenuBuilder& MenuBuilder, FName InParentName, FName InChildName, FToolMenuContext InMenuContext)
|
|
{
|
|
if (UToolMenu* MenuToUse = FindSubMenuToGenerateWith(InParentName, InChildName))
|
|
{
|
|
// Create combined final menu
|
|
UToolMenu* GeneratedMenu = GenerateMenu(MenuToUse->MenuName, InMenuContext);
|
|
|
|
// Override name that people should use to extend this menu (as it may not be implemented)
|
|
GeneratedMenu->MenuName = JoinMenuPaths(InParentName, InChildName);
|
|
|
|
// Populate menu builder with final menu
|
|
PopulateMenuBuilder(MenuBuilder, GeneratedMenu);
|
|
}
|
|
}
|
|
|
|
void UToolMenus::PopulateMenuBuilder(FMenuBuilder& MenuBuilder, UToolMenu* MenuData)
|
|
{
|
|
const bool bIsEditing = MenuData->IsEditing();
|
|
if (GetDisplayUIExtensionPoints() && !bIsEditing)
|
|
{
|
|
const FName MenuName = MenuData->GetMenuName();
|
|
MenuBuilder.AddMenuEntry(
|
|
FText::FromName(MenuName),
|
|
LOCTEXT("CopyMenuNameToClipboard", "Copy menu name to clipboard"),
|
|
FSlateIcon(),
|
|
FExecuteAction::CreateLambda([MenuName]() { FPlatformApplicationMisc::ClipboardCopy(*MenuName.ToString()); }),
|
|
"MenuName"
|
|
);
|
|
}
|
|
|
|
for (int i=0; i < MenuData->Sections.Num(); ++i)
|
|
{
|
|
FToolMenuSection& Section = MenuData->Sections[i];
|
|
if (Section.Construct.NewToolMenuDelegateLegacy.IsBound())
|
|
{
|
|
Section.Construct.NewToolMenuDelegateLegacy.Execute(MenuBuilder, MenuData);
|
|
continue;
|
|
}
|
|
|
|
if (bIsEditing)
|
|
{
|
|
// Always provide label when editing so we have area to drag/drop and hide sections
|
|
FText LabelText = Section.Label.Get();
|
|
if (LabelText.IsEmpty())
|
|
{
|
|
LabelText = FText::FromName(Section.Name);
|
|
}
|
|
MenuBuilder.BeginSection(Section.Name, LabelText);
|
|
}
|
|
else
|
|
{
|
|
MenuBuilder.BeginSection(Section.Name, Section.Label);
|
|
}
|
|
|
|
for (FToolMenuEntry& Block : Section.Blocks)
|
|
{
|
|
if (Block.ConstructLegacy.IsBound())
|
|
{
|
|
Block.ConstructLegacy.Execute(MenuBuilder, MenuData);
|
|
continue;
|
|
}
|
|
|
|
FUIAction UIAction = ConvertUIAction(Block, MenuData->Context);
|
|
|
|
TSharedPtr<SWidget> Widget;
|
|
if (Block.MakeWidget.IsBound())
|
|
{
|
|
Widget = Block.MakeWidget.Execute(MenuData->Context);
|
|
}
|
|
|
|
if (Block.Type == EMultiBlockType::MenuEntry)
|
|
{
|
|
if (Block.IsSubMenu())
|
|
{
|
|
FName SubMenuFullName = JoinMenuPaths(MenuData->MenuName, Block.Name);
|
|
FNewMenuDelegate NewMenuDelegate;
|
|
if (Block.SubMenuData.ConstructMenu.NewMenuDelegate.IsBound())
|
|
{
|
|
NewMenuDelegate = Block.SubMenuData.ConstructMenu.NewMenuDelegate;
|
|
}
|
|
else if (Block.SubMenuData.ConstructMenu.NewToolMenuDelegate.IsBound())
|
|
{
|
|
NewMenuDelegate = FNewMenuDelegate::CreateUObject(this, &UToolMenus::FillMenuDynamic, SubMenuFullName, Block.SubMenuData.ConstructMenu.NewToolMenuDelegate, MenuData->Context);
|
|
}
|
|
else
|
|
{
|
|
NewMenuDelegate = FNewMenuDelegate::CreateUObject(this, &UToolMenus::FillMenu, SubMenuFullName, MenuData->Context);
|
|
}
|
|
|
|
if (Widget.IsValid())
|
|
{
|
|
if (UIAction.IsBound())
|
|
{
|
|
MenuBuilder.AddSubMenu(UIAction, Widget.ToSharedRef(), NewMenuDelegate, Block.bShouldCloseWindowAfterMenuSelection);
|
|
}
|
|
else
|
|
{
|
|
MenuBuilder.AddSubMenu(Widget.ToSharedRef(), NewMenuDelegate, Block.SubMenuData.bOpenSubMenuOnClick, Block.bShouldCloseWindowAfterMenuSelection);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MenuBuilder.AddSubMenu(
|
|
Block.Label,
|
|
Block.ToolTip,
|
|
NewMenuDelegate,
|
|
Block.SubMenuData.bOpenSubMenuOnClick,
|
|
Block.Icon.Get(),
|
|
Block.bShouldCloseWindowAfterMenuSelection,
|
|
Block.Name);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (Block.Command.IsValid())
|
|
{
|
|
bool bPopCommandList = false;
|
|
TSharedPtr<const FUICommandList> CommandListForAction;
|
|
if (Block.GetActionForCommand(MenuData->Context, CommandListForAction) != nullptr && CommandListForAction.IsValid())
|
|
{
|
|
MenuBuilder.PushCommandList(CommandListForAction.ToSharedRef());
|
|
bPopCommandList = true;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogToolMenus, Error, TEXT("UI command not found for menu entry: %s, menu: %s"), *Block.Name.ToString(), *MenuData->MenuName.ToString());
|
|
}
|
|
|
|
MenuBuilder.AddMenuEntry(Block.Command, Block.Name, Block.Label, Block.ToolTip, Block.Icon.Get());
|
|
|
|
if (bPopCommandList)
|
|
{
|
|
MenuBuilder.PopCommandList();
|
|
}
|
|
}
|
|
else if (Block.ScriptObject)
|
|
{
|
|
UToolMenuEntryScript* ScriptObject = Block.ScriptObject;
|
|
const FSlateIcon Icon = ScriptObject->CreateIconAttribute(MenuData->Context).Get();
|
|
MenuBuilder.AddMenuEntry(ScriptObject->CreateLabelAttribute(MenuData->Context), ScriptObject->CreateToolTipAttribute(MenuData->Context), Icon, UIAction, ScriptObject->Data.Name, Block.UserInterfaceActionType, Block.TutorialHighlightName);
|
|
}
|
|
else
|
|
{
|
|
if (Widget.IsValid())
|
|
{
|
|
MenuBuilder.AddMenuEntry(UIAction, Widget.ToSharedRef(), Block.Name, Block.ToolTip, Block.UserInterfaceActionType, Block.TutorialHighlightName);
|
|
}
|
|
else
|
|
{
|
|
MenuBuilder.AddMenuEntry(Block.Label, Block.ToolTip, Block.Icon.Get(), UIAction, Block.Name, Block.UserInterfaceActionType, Block.TutorialHighlightName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (Block.Type == EMultiBlockType::MenuSeparator)
|
|
{
|
|
MenuBuilder.AddMenuSeparator(Block.Name);
|
|
}
|
|
else if (Block.Type == EMultiBlockType::Widget)
|
|
{
|
|
MenuBuilder.AddWidget(Widget.ToSharedRef(), Block.Label.Get(), Block.WidgetData.bNoIndent, Block.WidgetData.bSearchable);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogToolMenus, Warning, TEXT("Menu '%s', item '%s', type not currently supported: %d"), *MenuData->MenuName.ToString(), *Block.Name.ToString(), Block.Type);
|
|
}
|
|
}
|
|
|
|
MenuBuilder.EndSection();
|
|
}
|
|
|
|
AddReferencedContextObjects(MenuBuilder.GetMultiBox(), MenuData->Context);
|
|
}
|
|
|
|
void UToolMenus::PopulateToolBarBuilder(FToolBarBuilder& ToolBarBuilder, UToolMenu* MenuData)
|
|
{
|
|
for (FToolMenuSection& Section : MenuData->Sections)
|
|
{
|
|
if (Section.Construct.NewToolBarDelegateLegacy.IsBound())
|
|
{
|
|
Section.Construct.NewToolBarDelegateLegacy.Execute(ToolBarBuilder, MenuData);
|
|
continue;
|
|
}
|
|
|
|
ToolBarBuilder.BeginSection(Section.Name);
|
|
|
|
for (FToolMenuEntry& Block : Section.Blocks)
|
|
{
|
|
if (Block.ToolBarData.ConstructLegacy.IsBound())
|
|
{
|
|
Block.ToolBarData.ConstructLegacy.Execute(ToolBarBuilder, MenuData);
|
|
continue;
|
|
}
|
|
|
|
FUIAction UIAction = ConvertUIAction(Block, MenuData->Context);
|
|
|
|
TSharedPtr<SWidget> Widget;
|
|
if (Block.MakeWidget.IsBound())
|
|
{
|
|
Widget = Block.MakeWidget.Execute(MenuData->Context);
|
|
}
|
|
|
|
if (Block.Type == EMultiBlockType::ToolBarButton)
|
|
{
|
|
if (Block.Command.IsValid())
|
|
{
|
|
bool bPopCommandList = false;
|
|
TSharedPtr<const FUICommandList> CommandListForAction;
|
|
if (Block.GetActionForCommand(MenuData->Context, CommandListForAction) != nullptr && CommandListForAction.IsValid())
|
|
{
|
|
ToolBarBuilder.PushCommandList(CommandListForAction.ToSharedRef());
|
|
bPopCommandList = true;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogToolMenus, Error, TEXT("UI command not found for toolbar entry: %s, toolbar: %s"), *Block.Name.ToString(), *MenuData->MenuName.ToString());
|
|
}
|
|
|
|
ToolBarBuilder.AddToolBarButton(Block.Command, Block.Name, Block.Label, Block.ToolTip, Block.Icon, Block.TutorialHighlightName);
|
|
|
|
if (bPopCommandList)
|
|
{
|
|
ToolBarBuilder.PopCommandList();
|
|
}
|
|
}
|
|
else if (Block.ScriptObject)
|
|
{
|
|
UToolMenuEntryScript* ScriptObject = Block.ScriptObject;
|
|
TAttribute<FSlateIcon> Icon = ScriptObject->CreateIconAttribute(MenuData->Context);
|
|
ToolBarBuilder.AddToolBarButton(UIAction, ScriptObject->Data.Name, ScriptObject->CreateLabelAttribute(MenuData->Context), ScriptObject->CreateToolTipAttribute(MenuData->Context), Icon, Block.UserInterfaceActionType, Block.TutorialHighlightName);
|
|
}
|
|
else
|
|
{
|
|
ToolBarBuilder.AddToolBarButton(UIAction, Block.Name, Block.Label, Block.ToolTip, Block.Icon, Block.UserInterfaceActionType, Block.TutorialHighlightName);
|
|
}
|
|
}
|
|
else if (Block.Type == EMultiBlockType::ToolBarComboButton)
|
|
{
|
|
FOnGetContent OnGetContent = ConvertWidgetChoice(Block.ToolBarData.ComboButtonContextMenuGenerator, MenuData->Context);
|
|
if (OnGetContent.IsBound())
|
|
{
|
|
ToolBarBuilder.AddComboButton(UIAction, OnGetContent, Block.Label, Block.ToolTip, Block.Icon, Block.ToolBarData.bSimpleComboBox, Block.TutorialHighlightName);
|
|
}
|
|
else
|
|
{
|
|
FName SubMenuFullName = JoinMenuPaths(MenuData->MenuName, Block.Name);
|
|
FOnGetContent Delegate = FOnGetContent::CreateUObject(this, &UToolMenus::GenerateToolbarComboButtonMenu, SubMenuFullName, MenuData->Context);
|
|
ToolBarBuilder.AddComboButton(UIAction, Delegate, Block.Label, Block.ToolTip, Block.Icon, Block.ToolBarData.bSimpleComboBox, Block.TutorialHighlightName);
|
|
}
|
|
}
|
|
else if (Block.Type == EMultiBlockType::ToolBarSeparator)
|
|
{
|
|
ToolBarBuilder.AddSeparator(Block.Name);
|
|
}
|
|
else if (Block.Type == EMultiBlockType::Widget)
|
|
{
|
|
ToolBarBuilder.AddWidget(Widget.ToSharedRef(), Block.TutorialHighlightName, Block.WidgetData.bSearchable);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogToolMenus, Warning, TEXT("Toolbar '%s', item '%s', type not currently supported: %d"), *MenuData->MenuName.ToString(), *Block.Name.ToString(), Block.Type);
|
|
}
|
|
}
|
|
|
|
ToolBarBuilder.EndSection();
|
|
}
|
|
|
|
if (GetDisplayUIExtensionPoints())
|
|
{
|
|
const FName MenuName = MenuData->GetMenuName();
|
|
ToolBarBuilder.BeginSection(MenuName);
|
|
ToolBarBuilder.AddToolBarButton(
|
|
FExecuteAction::CreateLambda([MenuName]() { FPlatformApplicationMisc::ClipboardCopy(*MenuName.ToString()); }),
|
|
"MenuName",
|
|
LOCTEXT("CopyNameToClipboard", "Copy Name"),
|
|
LOCTEXT("CopyMenuNameToClipboard", "Copy menu name to clipboard")
|
|
);
|
|
ToolBarBuilder.EndSection();
|
|
}
|
|
|
|
AddReferencedContextObjects(ToolBarBuilder.GetMultiBox(), MenuData->Context);
|
|
}
|
|
|
|
void UToolMenus::PopulateMenuBarBuilder(FMenuBarBuilder& MenuBarBuilder, UToolMenu* GeneratedMenu)
|
|
{
|
|
if (GeneratedMenu->Sections.Num() > 0)
|
|
{
|
|
for (FToolMenuEntry& Block : GeneratedMenu->Sections[0].Blocks)
|
|
{
|
|
MenuBarBuilder.AddPullDownMenu(
|
|
Block.Label.Get(),
|
|
Block.ToolTip.Get(),
|
|
FNewMenuDelegate::CreateUObject(this, &UToolMenus::FillMenuBarDropDown, GeneratedMenu->MenuName, Block.Name, GeneratedMenu->Context),
|
|
Block.Name
|
|
);
|
|
}
|
|
|
|
AddReferencedContextObjects(MenuBarBuilder.GetMultiBox(), GeneratedMenu->Context);
|
|
}
|
|
}
|
|
|
|
FOnGetContent UToolMenus::ConvertWidgetChoice(const FNewToolMenuWidgetChoice& Choice, const FToolMenuContext& Context) const
|
|
{
|
|
if (Choice.NewToolMenuWidget.IsBound())
|
|
{
|
|
return FOnGetContent::CreateLambda([ToCall = Choice.NewToolMenuWidget, Context]()
|
|
{
|
|
if (ToCall.IsBound())
|
|
{
|
|
return ToCall.Execute(Context);
|
|
}
|
|
|
|
return SNullWidget::NullWidget;
|
|
});
|
|
}
|
|
else if (Choice.NewToolMenu.IsBound())
|
|
{
|
|
return FOnGetContent::CreateLambda([ToCall = Choice.NewToolMenu, Context]()
|
|
{
|
|
if (ToCall.IsBound())
|
|
{
|
|
UToolMenu* MenuData = NewObject<UToolMenu>();
|
|
MenuData->Context = Context;
|
|
ToCall.Execute(MenuData);
|
|
return UToolMenus::Get()->GenerateWidget(MenuData);
|
|
}
|
|
|
|
return SNullWidget::NullWidget;
|
|
});
|
|
}
|
|
return Choice.OnGetContent;
|
|
}
|
|
|
|
FUIAction UToolMenus::ConvertUIAction(const FToolMenuEntry& Block, const FToolMenuContext& Context)
|
|
{
|
|
FUIAction UIAction;
|
|
|
|
if (Block.ScriptObject)
|
|
{
|
|
UIAction = ConvertScriptObjectToUIAction(Block.ScriptObject, Context);
|
|
}
|
|
else
|
|
{
|
|
UIAction = ConvertUIAction(Block.Action, Context);
|
|
}
|
|
|
|
if (!UIAction.ExecuteAction.IsBound() && Block.StringExecuteAction.IsBound())
|
|
{
|
|
UIAction.ExecuteAction = Block.StringExecuteAction.ToExecuteAction(Context);
|
|
}
|
|
|
|
return UIAction;
|
|
}
|
|
|
|
FUIAction UToolMenus::ConvertUIAction(const FToolUIActionChoice& Choice, const FToolMenuContext& Context)
|
|
{
|
|
if (const FToolUIAction* ToolAction = Choice.GetToolUIAction())
|
|
{
|
|
return ConvertUIAction(*ToolAction, Context);
|
|
}
|
|
else if (const FToolDynamicUIAction* DynamicToolAction = Choice.GetToolDynamicUIAction())
|
|
{
|
|
return ConvertUIAction(*DynamicToolAction, Context);
|
|
}
|
|
else if (const FUIAction* Action = Choice.GetUIAction())
|
|
{
|
|
return *Action;
|
|
}
|
|
|
|
return FUIAction();
|
|
}
|
|
|
|
FUIAction UToolMenus::ConvertUIAction(const FToolUIAction& Actions, const FToolMenuContext& Context)
|
|
{
|
|
FUIAction UIAction;
|
|
|
|
if (Actions.ExecuteAction.IsBound())
|
|
{
|
|
UIAction.ExecuteAction.BindLambda([DelegateToCall = Actions.ExecuteAction, Context]()
|
|
{
|
|
DelegateToCall.ExecuteIfBound(Context);
|
|
});
|
|
}
|
|
|
|
if (Actions.CanExecuteAction.IsBound())
|
|
{
|
|
UIAction.CanExecuteAction.BindLambda([DelegateToCall = Actions.CanExecuteAction, Context]()
|
|
{
|
|
return DelegateToCall.Execute(Context);
|
|
});
|
|
}
|
|
|
|
if (Actions.GetActionCheckState.IsBound())
|
|
{
|
|
UIAction.GetActionCheckState.BindLambda([DelegateToCall = Actions.GetActionCheckState, Context]()
|
|
{
|
|
return DelegateToCall.Execute(Context);
|
|
});
|
|
}
|
|
|
|
if (Actions.IsActionVisibleDelegate.IsBound())
|
|
{
|
|
UIAction.IsActionVisibleDelegate.BindLambda([DelegateToCall = Actions.IsActionVisibleDelegate, Context]()
|
|
{
|
|
return DelegateToCall.Execute(Context);
|
|
});
|
|
}
|
|
|
|
return UIAction;
|
|
}
|
|
|
|
FUIAction UToolMenus::ConvertUIAction(const FToolDynamicUIAction& Actions, const FToolMenuContext& Context)
|
|
{
|
|
FUIAction UIAction;
|
|
|
|
if (Actions.ExecuteAction.IsBound())
|
|
{
|
|
UIAction.ExecuteAction.BindLambda([DelegateToCall = Actions.ExecuteAction, Context]()
|
|
{
|
|
DelegateToCall.ExecuteIfBound(Context);
|
|
});
|
|
}
|
|
|
|
if (Actions.CanExecuteAction.IsBound())
|
|
{
|
|
UIAction.CanExecuteAction.BindLambda([DelegateToCall = Actions.CanExecuteAction, Context]()
|
|
{
|
|
return DelegateToCall.Execute(Context);
|
|
});
|
|
}
|
|
|
|
if (Actions.GetActionCheckState.IsBound())
|
|
{
|
|
UIAction.GetActionCheckState.BindLambda([DelegateToCall = Actions.GetActionCheckState, Context]()
|
|
{
|
|
return DelegateToCall.Execute(Context);
|
|
});
|
|
}
|
|
|
|
if (Actions.IsActionVisibleDelegate.IsBound())
|
|
{
|
|
UIAction.IsActionVisibleDelegate.BindLambda([DelegateToCall = Actions.IsActionVisibleDelegate, Context]()
|
|
{
|
|
return DelegateToCall.Execute(Context);
|
|
});
|
|
}
|
|
|
|
return UIAction;
|
|
}
|
|
|
|
FUIAction UToolMenus::ConvertScriptObjectToUIAction(UToolMenuEntryScript* ScriptObject, const FToolMenuContext& Context)
|
|
{
|
|
FUIAction UIAction;
|
|
|
|
if (ScriptObject)
|
|
{
|
|
UClass* ScriptClass = ScriptObject->GetClass();
|
|
|
|
static const FName ExecuteName = GET_FUNCTION_NAME_CHECKED(UToolMenuEntryScript, Execute);
|
|
if (ScriptClass->IsFunctionImplementedInScript(ExecuteName))
|
|
{
|
|
UIAction.ExecuteAction.BindUFunction(ScriptObject, ExecuteName, Context);
|
|
}
|
|
|
|
static const FName CanExecuteName = GET_FUNCTION_NAME_CHECKED(UToolMenuEntryScript, CanExecute);
|
|
if (ScriptClass->IsFunctionImplementedInScript(CanExecuteName))
|
|
{
|
|
UIAction.CanExecuteAction.BindUFunction(ScriptObject, CanExecuteName, Context);
|
|
}
|
|
|
|
static const FName GetCheckStateName = GET_FUNCTION_NAME_CHECKED(UToolMenuEntryScript, GetCheckState);
|
|
if (ScriptClass->IsFunctionImplementedInScript(GetCheckStateName))
|
|
{
|
|
UIAction.GetActionCheckState.BindUFunction(ScriptObject, GetCheckStateName, Context);
|
|
}
|
|
|
|
static const FName IsVisibleName = GET_FUNCTION_NAME_CHECKED(UToolMenuEntryScript, IsVisible);
|
|
if (ScriptClass->IsFunctionImplementedInScript(IsVisibleName))
|
|
{
|
|
UIAction.IsActionVisibleDelegate.BindUFunction(ScriptObject, IsVisibleName, Context);
|
|
}
|
|
}
|
|
|
|
return UIAction;
|
|
}
|
|
|
|
void UToolMenus::ExecuteStringCommand(const FToolMenuStringCommand StringCommand, const FToolMenuContext Context)
|
|
{
|
|
if (StringCommand.IsBound())
|
|
{
|
|
const FName TypeName = StringCommand.GetTypeName();
|
|
UToolMenus* ToolMenus = UToolMenus::Get();
|
|
if (const FToolMenuExecuteString* Handler = ToolMenus->StringCommandHandlers.Find(TypeName))
|
|
{
|
|
if (Handler->IsBound())
|
|
{
|
|
Handler->Execute(StringCommand.String, Context);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogToolMenus, Warning, TEXT("Unknown string command handler type: '%s'"), *TypeName.ToString());
|
|
}
|
|
}
|
|
}
|
|
|
|
UToolMenu* UToolMenus::FindSubMenuToGenerateWith(const FName InParentName, const FName InChildName)
|
|
{
|
|
FName BaseName = InParentName;
|
|
while (BaseName != NAME_None)
|
|
{
|
|
FName JoinedName = JoinMenuPaths(BaseName, InChildName);
|
|
if (UToolMenu* Found = FindMenu(JoinedName))
|
|
{
|
|
return Found;
|
|
}
|
|
|
|
UToolMenu* BaseData = FindMenu(BaseName);
|
|
BaseName = BaseData ? BaseData->MenuParent : NAME_None;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
UObject* UToolMenus::FindContext(const FToolMenuContext& InContext, UClass* InClass)
|
|
{
|
|
return InContext.FindByClass(InClass);
|
|
}
|
|
|
|
void UToolMenus::AddReferencedContextObjects(const TSharedRef<FMultiBox>& InMultiBox, const FToolMenuContext& InMenuContext)
|
|
{
|
|
if (InMenuContext.ContextObjects.Num() == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
TArray<UObject*>& References = WidgetObjectReferences.FindOrAdd(InMultiBox);
|
|
for (const TWeakObjectPtr<UObject>& WeakObject : InMenuContext.ContextObjects)
|
|
{
|
|
if (UObject* Object = WeakObject.Get())
|
|
{
|
|
References.AddUnique(Object);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UToolMenus::AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector)
|
|
{
|
|
UToolMenus* This = CastChecked<UToolMenus>(InThis);
|
|
|
|
for (auto It = This->WidgetObjectReferences.CreateIterator(); It; ++It)
|
|
{
|
|
if (It->Key.IsValid())
|
|
{
|
|
Collector.AddReferencedObjects(It->Value, InThis);
|
|
}
|
|
else
|
|
{
|
|
It.RemoveCurrent();
|
|
}
|
|
}
|
|
|
|
Super::AddReferencedObjects(InThis, Collector);
|
|
}
|
|
|
|
UToolMenu* UToolMenus::GenerateMenu(const FName Name, const FToolMenuContext& InMenuContext)
|
|
{
|
|
return GenerateMenu(CollectHierarchy(Name), InMenuContext);
|
|
}
|
|
|
|
UToolMenu* UToolMenus::GenerateMenu(const TArray<UToolMenu*>& Hierarchy, const FToolMenuContext& InMenuContext)
|
|
{
|
|
UToolMenu* GeneratedMenu = NewObject<UToolMenu>(this);
|
|
|
|
if (Hierarchy.Num() > 0)
|
|
{
|
|
GeneratedMenu->InitGeneratedCopy(Hierarchy[0], Hierarchy.Last()->MenuName, &InMenuContext);
|
|
AssembleMenuHierarchy(GeneratedMenu, Hierarchy);
|
|
}
|
|
|
|
return GeneratedMenu;
|
|
}
|
|
|
|
TSharedRef< class SWidget > UToolMenus::GenerateWidget(const FName InName, const FToolMenuContext& InMenuContext)
|
|
{
|
|
UToolMenu* Generated = GenerateMenu(InName, InMenuContext);
|
|
return GenerateWidget(Generated);
|
|
}
|
|
|
|
TSharedRef<SWidget> UToolMenus::GenerateWidget(const TArray<UToolMenu*>& Hierarchy, const FToolMenuContext& InMenuContext)
|
|
{
|
|
if (Hierarchy.Num() == 0)
|
|
{
|
|
return SNullWidget::NullWidget;
|
|
}
|
|
|
|
UToolMenu* Generated = GenerateMenu(Hierarchy, InMenuContext);
|
|
return GenerateWidget(Generated);
|
|
}
|
|
|
|
TSharedRef<SWidget> UToolMenus::GenerateWidget(UToolMenu* GeneratedMenu)
|
|
{
|
|
CleanupStaleWidgetsNextTick();
|
|
|
|
FGeneratedToolMenuWidgets& WidgetsForMenuName = GeneratedMenuWidgets.FindOrAdd(GeneratedMenu->MenuName);
|
|
|
|
// Store a copy so that we can call 'Refresh' on menus not in the database
|
|
FGeneratedToolMenuWidget& GeneratedMenuWidget = WidgetsForMenuName.Instances.AddDefaulted_GetRef();
|
|
GeneratedMenuWidget.GeneratedMenu = DuplicateObject<UToolMenu>(GeneratedMenu, this);
|
|
// Copy native properties that serialize does not
|
|
GeneratedMenuWidget.GeneratedMenu->Context = GeneratedMenu->Context;
|
|
GeneratedMenuWidget.GeneratedMenu->StyleSet = GeneratedMenu->StyleSet;
|
|
|
|
if (GeneratedMenu->IsEditing())
|
|
{
|
|
// Convert toolbar into menu during editing
|
|
if (GeneratedMenu->MenuType == EMultiBoxType::ToolBar || GeneratedMenu->MenuType == EMultiBoxType::VerticalToolBar)
|
|
{
|
|
for (FToolMenuSection& Section : GeneratedMenu->Sections)
|
|
{
|
|
for (FToolMenuEntry& Entry : Section.Blocks)
|
|
{
|
|
ModifyEntryForEditDialog(Entry);
|
|
}
|
|
}
|
|
}
|
|
|
|
FMenuBuilder MenuBuilder(GeneratedMenu->bShouldCloseWindowAfterMenuSelection, GeneratedMenu->Context.CommandList, GeneratedMenu->Context.GetAllExtenders(), GeneratedMenu->bCloseSelfOnly, GeneratedMenu->StyleSet, GeneratedMenu->bSearchable, GeneratedMenu->MenuName);
|
|
PopulateMenuBuilder(MenuBuilder, GeneratedMenu);
|
|
TSharedRef<SWidget> Result = MenuBuilder.MakeWidget();
|
|
GeneratedMenuWidget.Widget = Result;
|
|
return Result;
|
|
}
|
|
else if (GeneratedMenu->MenuType == EMultiBoxType::Menu)
|
|
{
|
|
FMenuBuilder MenuBuilder(GeneratedMenu->bShouldCloseWindowAfterMenuSelection, GeneratedMenu->Context.CommandList, GeneratedMenu->Context.GetAllExtenders(), GeneratedMenu->bCloseSelfOnly, GeneratedMenu->StyleSet, GeneratedMenu->bSearchable, GeneratedMenu->MenuName);
|
|
PopulateMenuBuilder(MenuBuilder, GeneratedMenu);
|
|
TSharedRef<SWidget> Result = MenuBuilder.MakeWidget();
|
|
GeneratedMenuWidget.Widget = Result;
|
|
return Result;
|
|
}
|
|
else if (GeneratedMenu->MenuType == EMultiBoxType::MenuBar)
|
|
{
|
|
FMenuBarBuilder MenuBarBuilder(GeneratedMenu->Context.CommandList, GeneratedMenu->Context.GetAllExtenders(), GeneratedMenu->StyleSet, GeneratedMenu->MenuName);
|
|
PopulateMenuBarBuilder(MenuBarBuilder, GeneratedMenu);
|
|
TSharedRef<SWidget> Result = MenuBarBuilder.MakeWidget();
|
|
GeneratedMenuWidget.Widget = Result;
|
|
return Result;
|
|
}
|
|
else if (GeneratedMenu->MenuType == EMultiBoxType::ToolBar || GeneratedMenu->MenuType == EMultiBoxType::VerticalToolBar)
|
|
{
|
|
const EOrientation ToolBarOrientation = (GeneratedMenu->MenuType == EMultiBoxType::VerticalToolBar) ? Orient_Vertical : Orient_Horizontal;
|
|
FToolBarBuilder ToolbarBuilder(GeneratedMenu->Context.CommandList, GeneratedMenu->MenuName, GeneratedMenu->Context.GetAllExtenders(), ToolBarOrientation, GeneratedMenu->bToolBarForceSmallIcons);
|
|
ToolbarBuilder.SetIsFocusable(GeneratedMenu->bToolBarIsFocusable);
|
|
PopulateToolBarBuilder(ToolbarBuilder, GeneratedMenu);
|
|
TSharedRef<SWidget> Result = ToolbarBuilder.MakeWidget();
|
|
GeneratedMenuWidget.Widget = Result;
|
|
return Result;
|
|
}
|
|
|
|
return SNullWidget::NullWidget;
|
|
}
|
|
|
|
void UToolMenus::ModifyEntryForEditDialog(FToolMenuEntry& Entry)
|
|
{
|
|
if (Entry.Type == EMultiBlockType::ToolBarButton)
|
|
{
|
|
Entry.Type = EMultiBlockType::MenuEntry;
|
|
}
|
|
else if (Entry.Type == EMultiBlockType::ToolBarComboButton)
|
|
{
|
|
Entry.Type = EMultiBlockType::MenuEntry;
|
|
if (Entry.ToolBarData.bSimpleComboBox)
|
|
{
|
|
Entry.SubMenuData.bIsSubMenu = true;
|
|
}
|
|
}
|
|
else if (Entry.Type == EMultiBlockType::ToolBarSeparator)
|
|
{
|
|
Entry.Type = EMultiBlockType::MenuSeparator;
|
|
}
|
|
}
|
|
|
|
void UToolMenus::AssignSetTimerForNextTickDelegate(const FSimpleDelegate& InDelegate)
|
|
{
|
|
SetTimerForNextTickDelegate = InDelegate;
|
|
}
|
|
|
|
void UToolMenus::SetNextTickTimer()
|
|
{
|
|
if (!bNextTickTimerIsSet)
|
|
{
|
|
bNextTickTimerIsSet = true;
|
|
|
|
SetTimerForNextTickDelegate.ExecuteIfBound();
|
|
}
|
|
}
|
|
|
|
void UToolMenus::CleanupStaleWidgetsNextTick()
|
|
{
|
|
bCleanupStaleWidgetsNextTick = true;
|
|
SetNextTickTimer();
|
|
}
|
|
|
|
void UToolMenus::RefreshAllWidgets()
|
|
{
|
|
bRefreshWidgetsNextTick = true;
|
|
SetNextTickTimer();
|
|
}
|
|
|
|
void UToolMenus::HandleNextTick()
|
|
{
|
|
if (bCleanupStaleWidgetsNextTick || bRefreshWidgetsNextTick)
|
|
{
|
|
CleanupStaleWidgets();
|
|
bCleanupStaleWidgetsNextTick = false;
|
|
|
|
if (bRefreshWidgetsNextTick)
|
|
{
|
|
for (auto WidgetsForMenuNameIt = GeneratedMenuWidgets.CreateIterator(); WidgetsForMenuNameIt; ++WidgetsForMenuNameIt)
|
|
{
|
|
FGeneratedToolMenuWidgets& WidgetsForMenuName = WidgetsForMenuNameIt->Value;
|
|
for (auto Instance = WidgetsForMenuName.Instances.CreateIterator(); Instance; ++Instance)
|
|
{
|
|
if (Instance->Widget.IsValid())
|
|
{
|
|
RefreshMenuWidget(WidgetsForMenuNameIt->Key, *Instance);
|
|
}
|
|
}
|
|
}
|
|
|
|
bRefreshWidgetsNextTick = false;
|
|
}
|
|
}
|
|
|
|
bNextTickTimerIsSet = false;
|
|
}
|
|
|
|
void UToolMenus::CleanupStaleWidgets()
|
|
{
|
|
for (auto WidgetsForMenuNameIt = GeneratedMenuWidgets.CreateIterator(); WidgetsForMenuNameIt; ++WidgetsForMenuNameIt)
|
|
{
|
|
FGeneratedToolMenuWidgets& WidgetsForMenuName = WidgetsForMenuNameIt->Value;
|
|
|
|
for (auto Instance = WidgetsForMenuName.Instances.CreateIterator(); Instance; ++Instance)
|
|
{
|
|
if (!Instance->Widget.IsValid())
|
|
{
|
|
Instance.RemoveCurrent();
|
|
}
|
|
}
|
|
|
|
if (WidgetsForMenuName.Instances.Num() == 0)
|
|
{
|
|
WidgetsForMenuNameIt.RemoveCurrent();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool UToolMenus::RefreshMenuWidget(const FName InName)
|
|
{
|
|
if (FGeneratedToolMenuWidgets* WidgetsForMenuName = GeneratedMenuWidgets.Find(InName))
|
|
{
|
|
for (auto Instance = WidgetsForMenuName->Instances.CreateIterator(); Instance; ++Instance)
|
|
{
|
|
if (RefreshMenuWidget(InName, *Instance))
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
Instance.RemoveCurrent();
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool UToolMenus::RefreshMenuWidget(const FName InName, FGeneratedToolMenuWidget& GeneratedMenuWidget)
|
|
{
|
|
if (!GeneratedMenuWidget.Widget.IsValid())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Regenerate menu from database
|
|
UToolMenu* GeneratedMenu = GenerateMenu(InName, GeneratedMenuWidget.GeneratedMenu->Context);
|
|
GeneratedMenuWidget.GeneratedMenu = GeneratedMenu;
|
|
|
|
// Regenerate Multibox
|
|
TSharedRef<SMultiBoxWidget> MultiBoxWidget = StaticCastSharedRef<SMultiBoxWidget>(GeneratedMenuWidget.Widget.Pin().ToSharedRef());
|
|
if (GeneratedMenu->MenuType == EMultiBoxType::Menu)
|
|
{
|
|
FMenuBuilder MenuBuilder(GeneratedMenu->bShouldCloseWindowAfterMenuSelection, GeneratedMenu->Context.CommandList, GeneratedMenu->Context.GetAllExtenders(), GeneratedMenu->bCloseSelfOnly, GeneratedMenu->StyleSet, GeneratedMenu->bSearchable);
|
|
PopulateMenuBuilder(MenuBuilder, GeneratedMenu);
|
|
MultiBoxWidget->SetMultiBox(MenuBuilder.GetMultiBox());
|
|
}
|
|
else if (GeneratedMenu->MenuType == EMultiBoxType::MenuBar)
|
|
{
|
|
FMenuBarBuilder MenuBarBuilder(GeneratedMenu->Context.CommandList, GeneratedMenu->Context.GetAllExtenders(), GeneratedMenu->StyleSet);
|
|
PopulateMenuBarBuilder(MenuBarBuilder, GeneratedMenu);
|
|
MultiBoxWidget->SetMultiBox(MenuBarBuilder.GetMultiBox());
|
|
}
|
|
else if (GeneratedMenu->MenuType == EMultiBoxType::ToolBar || GeneratedMenu->MenuType == EMultiBoxType::VerticalToolBar)
|
|
{
|
|
const EOrientation ToolBarOrientation = (GeneratedMenu->MenuType == EMultiBoxType::VerticalToolBar) ? Orient_Vertical : Orient_Horizontal;
|
|
FToolBarBuilder ToolbarBuilder(GeneratedMenu->Context.CommandList, GeneratedMenu->MenuName, GeneratedMenu->Context.GetAllExtenders(), ToolBarOrientation, GeneratedMenu->bToolBarForceSmallIcons);
|
|
ToolbarBuilder.SetIsFocusable(GeneratedMenu->bToolBarIsFocusable);
|
|
PopulateToolBarBuilder(ToolbarBuilder, GeneratedMenu);
|
|
MultiBoxWidget->SetMultiBox(ToolbarBuilder.GetMultiBox());
|
|
}
|
|
|
|
MultiBoxWidget->BuildMultiBoxWidget();
|
|
return true;
|
|
}
|
|
|
|
UToolMenu* UToolMenus::GenerateMenuAsBuilder(const UToolMenu* InMenu, const FToolMenuContext& InMenuContext)
|
|
{
|
|
TArray<UToolMenu*> Hierarchy = CollectHierarchy(InMenu->MenuName);
|
|
|
|
// Insert InMenu as second to last so items in InMenu appear before items registered in database by other plugins
|
|
if (Hierarchy.Num() > 0)
|
|
{
|
|
Hierarchy.Insert((UToolMenu*)InMenu, Hierarchy.Num() - 1);
|
|
}
|
|
else
|
|
{
|
|
Hierarchy.Add((UToolMenu*)InMenu);
|
|
}
|
|
|
|
return GenerateMenu(Hierarchy, InMenuContext);
|
|
}
|
|
|
|
UToolMenu* UToolMenus::RegisterMenu(const FName InName, const FName InParent, EMultiBoxType InType, bool bWarnIfAlreadyRegistered)
|
|
{
|
|
if (UToolMenu* Found = FindMenu(InName))
|
|
{
|
|
if (!Found->bRegistered)
|
|
{
|
|
Found->MenuParent = InParent;
|
|
Found->MenuType = InType;
|
|
Found->MenuOwner = CurrentOwner();
|
|
Found->bRegistered = true;
|
|
}
|
|
else if (bWarnIfAlreadyRegistered)
|
|
{
|
|
UE_LOG(LogToolMenus, Warning, TEXT("Menu already registered : %s"), *InName.ToString());
|
|
}
|
|
|
|
return Found;
|
|
}
|
|
|
|
UToolMenu* ToolMenu = NewObject<UToolMenu>(this);
|
|
ToolMenu->InitMenu(CurrentOwner(), InName, InParent, InType);
|
|
ToolMenu->bRegistered = true;
|
|
Menus.Add(InName, ToolMenu);
|
|
return ToolMenu;
|
|
}
|
|
|
|
UToolMenu* UToolMenus::ExtendMenu(const FName InName)
|
|
{
|
|
if (UToolMenu* Found = FindMenu(InName))
|
|
{
|
|
return Found;
|
|
}
|
|
|
|
UToolMenu* ToolMenu = NewObject<UToolMenu>(this);
|
|
ToolMenu->MenuName = InName;
|
|
ToolMenu->bRegistered = false;
|
|
Menus.Add(InName, ToolMenu);
|
|
return ToolMenu;
|
|
}
|
|
|
|
void UToolMenus::RemoveMenu(const FName MenuName)
|
|
{
|
|
Menus.Remove(MenuName);
|
|
}
|
|
|
|
bool UToolMenus::AddMenuEntryObject(UToolMenuEntryScript* MenuEntryObject)
|
|
{
|
|
UToolMenu* Menu = UToolMenus::Get()->ExtendMenu(MenuEntryObject->Data.Menu);
|
|
Menu->AddMenuEntryObject(MenuEntryObject);
|
|
return true;
|
|
}
|
|
|
|
void UToolMenus::SetSectionLabel(const FName MenuName, const FName SectionName, const FText Label)
|
|
{
|
|
ExtendMenu(MenuName)->FindOrAddSection(SectionName).Label = TAttribute<FText>(Label);
|
|
}
|
|
|
|
void UToolMenus::SetSectionPosition(const FName MenuName, const FName SectionName, const FName PositionName, const EToolMenuInsertType PositionType)
|
|
{
|
|
ExtendMenu(MenuName)->FindOrAddSection(SectionName).InsertPosition = FToolMenuInsert(PositionName, PositionType);
|
|
}
|
|
|
|
void UToolMenus::AddSection(const FName MenuName, const FName SectionName, const TAttribute< FText >& InLabel, const FToolMenuInsert InPosition)
|
|
{
|
|
UToolMenu* Menu = ExtendMenu(MenuName);
|
|
FToolMenuSection* Section = Menu->FindSection(SectionName);
|
|
if (!Section)
|
|
{
|
|
Menu->AddSection(SectionName, InLabel, InPosition);
|
|
}
|
|
}
|
|
|
|
void UToolMenus::RemoveSection(const FName MenuName, const FName InSection)
|
|
{
|
|
if (UToolMenu* Menu = FindMenu(MenuName))
|
|
{
|
|
Menu->RemoveSection(InSection);
|
|
}
|
|
}
|
|
|
|
void UToolMenus::AddEntry(const FName MenuName, const FName InSection, const FToolMenuEntry& InEntry)
|
|
{
|
|
ExtendMenu(MenuName)->FindOrAddSection(InSection).AddEntry(InEntry);
|
|
}
|
|
|
|
void UToolMenus::RemoveEntry(const FName MenuName, const FName InSection, const FName InName)
|
|
{
|
|
if (UToolMenu* Menu = FindMenu(MenuName))
|
|
{
|
|
if (FToolMenuSection* Section = Menu->FindSection(InSection))
|
|
{
|
|
Section->RemoveEntry(InName);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UToolMenus::UnregisterOwnerInternal(FToolMenuOwner InOwner)
|
|
{
|
|
if (InOwner != FToolMenuOwner())
|
|
{
|
|
for (auto It = Menus.CreateIterator(); It; ++It)
|
|
{
|
|
int32 NumEntriesRemoved = 0;
|
|
|
|
UToolMenu* Menu = It->Value;
|
|
for (FToolMenuSection& Section : Menu->Sections)
|
|
{
|
|
NumEntriesRemoved += Section.RemoveEntriesByOwner(InOwner);
|
|
}
|
|
|
|
// Refresh any widgets that are currently displayed to the user
|
|
if (NumEntriesRemoved > 0)
|
|
{
|
|
RefreshAllWidgets();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FToolMenuOwner UToolMenus::CurrentOwner() const
|
|
{
|
|
if (OwnerStack.Num() > 0)
|
|
{
|
|
return OwnerStack.Last();
|
|
}
|
|
|
|
return FToolMenuOwner();
|
|
}
|
|
|
|
void UToolMenus::PushOwner(const FToolMenuOwner InOwner)
|
|
{
|
|
OwnerStack.Add(InOwner);
|
|
}
|
|
|
|
void UToolMenus::PopOwner(const FToolMenuOwner InOwner)
|
|
{
|
|
FToolMenuOwner PoppedOwner = OwnerStack.Pop(false);
|
|
check(PoppedOwner == InOwner);
|
|
}
|
|
|
|
void UToolMenus::UnregisterOwnerByName(FName InOwnerName)
|
|
{
|
|
UnregisterOwnerInternal(InOwnerName);
|
|
}
|
|
|
|
void UToolMenus::RegisterStringCommandHandler(const FName InName, const FToolMenuExecuteString& InDelegate)
|
|
{
|
|
StringCommandHandlers.Add(InName, InDelegate);
|
|
}
|
|
|
|
void UToolMenus::UnregisterStringCommandHandler(const FName InName)
|
|
{
|
|
StringCommandHandlers.Remove(InName);
|
|
}
|