3594 lines
105 KiB
C++
3594 lines
105 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "ToolMenus.h"
|
|
#include "IToolMenusModule.h"
|
|
#include "ToolMenusLog.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 "Framework/MultiBox/SToolBarButtonBlock.h"
|
|
#include "Internationalization/Internationalization.h"
|
|
#include "UObject/WeakObjectPtrTemplates.h"
|
|
|
|
#include "HAL/PlatformApplicationMisc.h" // For clipboard
|
|
#include "Styling/ToolBarStyle.h"
|
|
#include "Widgets/Layout/SScrollBox.h"
|
|
#include "Widgets/Layout/SSpacer.h"
|
|
|
|
#include UE_INLINE_GENERATED_CPP_BY_NAME(ToolMenus)
|
|
|
|
#define LOCTEXT_NAMESPACE "ToolMenuSubsystem"
|
|
|
|
DEFINE_LOG_CATEGORY(LogToolMenus);
|
|
|
|
namespace UE::ToolMenus::Private
|
|
{
|
|
|
|
UToolMenus* CreateToolMenusInstance()
|
|
{
|
|
UToolMenus* Instance = NewObject<UToolMenus>();
|
|
Instance->AddToRoot();
|
|
check(Instance);
|
|
return Instance;
|
|
}
|
|
|
|
/** Combine two visibility attributes with the first attribute's non-visibility taking precedence. */
|
|
FToolMenuVisibilityChoice CombineVisibility(const FToolMenuVisibilityChoice& PrimaryVisibility, const FToolMenuVisibilityChoice& SecondaryVisibility)
|
|
{
|
|
if (PrimaryVisibility.IsSet() && SecondaryVisibility.IsSet())
|
|
{
|
|
return TAttribute<EVisibility>::CreateLambda(
|
|
[Primary = PrimaryVisibility, Secondary = SecondaryVisibility]()
|
|
{
|
|
const EVisibility PrimaryValue = Primary.Get();
|
|
if (PrimaryValue != EVisibility::Visible)
|
|
{
|
|
return PrimaryValue;
|
|
}
|
|
return Secondary.Get();
|
|
}
|
|
);
|
|
}
|
|
|
|
if (PrimaryVisibility.IsSet())
|
|
{
|
|
return PrimaryVisibility;
|
|
}
|
|
|
|
if (SecondaryVisibility.IsSet())
|
|
{
|
|
return SecondaryVisibility;
|
|
}
|
|
|
|
return FToolMenuVisibilityChoice();
|
|
}
|
|
|
|
int32 SortSectionAlignment(EToolMenuSectionAlign A, EToolMenuSectionAlign B)
|
|
{
|
|
if (A == B)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (A == EToolMenuSectionAlign::First && B == EToolMenuSectionAlign::Default)
|
|
{
|
|
return -1;
|
|
}
|
|
if (A == EToolMenuSectionAlign::Default && B == EToolMenuSectionAlign::First)
|
|
{
|
|
return 1;
|
|
}
|
|
return A > B ? 1 : -1;
|
|
}
|
|
|
|
} // namespace UE::ToolMenus::Private
|
|
|
|
namespace UE::ToolMenus
|
|
{
|
|
|
|
FSubBlockReference::FSubBlockReference()
|
|
: ParentMenu(nullptr)
|
|
, Section(nullptr)
|
|
, Entry(nullptr)
|
|
{
|
|
}
|
|
|
|
FSubBlockReference::FSubBlockReference(const FSubBlockReference& Other)
|
|
: ParentMenu(Other.ParentMenu)
|
|
, Section(Other.Section)
|
|
, Entry(Other.Entry)
|
|
{
|
|
}
|
|
|
|
FSubBlockReference::FSubBlockReference(UToolMenu* InParent, FToolMenuSection* InSection, FToolMenuEntry* InEntry)
|
|
: ParentMenu(InParent)
|
|
, Section(InSection)
|
|
, Entry(InEntry)
|
|
{
|
|
}
|
|
|
|
|
|
} // namespace UE::ToolMenus
|
|
|
|
UToolMenus* UToolMenus::Singleton = nullptr;
|
|
bool UToolMenus::bHasShutDown = false;
|
|
FSimpleMulticastDelegate UToolMenus::StartupCallbacks;
|
|
TOptional<FDelegateHandle> UToolMenus::InternalStartupCallbackHandle;
|
|
|
|
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(FName MenuName, const FToolMenuContext& Context) const
|
|
{
|
|
if (IsBound())
|
|
{
|
|
return FExecuteAction::CreateStatic(&UToolMenus::ExecuteStringCommand, *this, MenuName, 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();
|
|
}
|
|
}
|
|
}
|
|
|
|
class FPopulateMenuBuilderWithToolMenuEntry
|
|
{
|
|
public:
|
|
FPopulateMenuBuilderWithToolMenuEntry(FMenuBuilder& InMenuBuilder, UToolMenu* InMenuData, FToolMenuSection& InSection, FToolMenuEntry& InBlock, bool bInAllowSubMenuCollapse) :
|
|
MenuBuilder(InMenuBuilder),
|
|
MenuData(InMenuData),
|
|
Section(InSection),
|
|
Block(InBlock),
|
|
BlockNameOverride(InBlock.Name),
|
|
bAllowSubMenuCollapse(bInAllowSubMenuCollapse),
|
|
bIsEditing(InMenuData->IsEditing())
|
|
{
|
|
}
|
|
|
|
void AddSubMenuEntryToMenuBuilder()
|
|
{
|
|
FName SubMenuFullName = UToolMenus::JoinMenuPaths(MenuData->MenuName, BlockNameOverride);
|
|
FNewMenuDelegate NewMenuDelegate;
|
|
bool bSubMenuAdded = false;
|
|
|
|
if (Block.SubMenuData.ConstructMenu.NewMenuLegacy.IsBound())
|
|
{
|
|
NewMenuDelegate = Block.SubMenuData.ConstructMenu.NewMenuLegacy;
|
|
}
|
|
else if (Block.SubMenuData.ConstructMenu.NewToolMenuWidget.IsBound() || Block.SubMenuData.ConstructMenu.OnGetContent.IsBound())
|
|
{
|
|
// Full replacement of the widget shown when submenu is opened
|
|
FOnGetContent OnGetContent = UToolMenus::Get()->ConvertWidgetChoice(Block.SubMenuData.ConstructMenu, MenuData->Context);
|
|
if (OnGetContent.IsBound())
|
|
{
|
|
MenuBuilder.AddWrapperSubMenu(
|
|
Block.Label.Get(),
|
|
Block.ToolTip.Get(),
|
|
OnGetContent,
|
|
Block.Icon.Get()
|
|
);
|
|
}
|
|
bSubMenuAdded = true;
|
|
}
|
|
else if (BlockNameOverride == NAME_None)
|
|
{
|
|
if (Block.SubMenuData.ConstructMenu.NewToolMenu.IsBound())
|
|
{
|
|
// Blocks with no name cannot call PopulateSubMenu()
|
|
NewMenuDelegate = FNewMenuDelegate::CreateUObject(UToolMenus::Get(), &UToolMenus::PopulateSubMenuWithoutName, TWeakObjectPtr<UToolMenu>(MenuData), Block);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogToolMenus, Warning, TEXT("Submenu that has no name is missing required delegate: %s"), *MenuData->MenuName.ToString());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (Block.SubMenuData.bAutoCollapse && bAllowSubMenuCollapse)
|
|
{
|
|
// Preview the submenu to see if it should be collapsed
|
|
UToolMenu* GeneratedSubMenu = UToolMenus::Get()->GenerateSubMenu(MenuData, BlockNameOverride);
|
|
if (GeneratedSubMenu)
|
|
{
|
|
int32 NumSubMenuEntries = 0;
|
|
FToolMenuEntry* FirstEntry = nullptr;
|
|
for (FToolMenuSection& SubMenuSection : GeneratedSubMenu->Sections)
|
|
{
|
|
NumSubMenuEntries += SubMenuSection.Blocks.Num();
|
|
if (!FirstEntry && SubMenuSection.Blocks.Num() > 0)
|
|
{
|
|
FirstEntry = &SubMenuSection.Blocks[0];
|
|
}
|
|
}
|
|
|
|
if (NumSubMenuEntries == 1)
|
|
{
|
|
// Use bAllowSubMenuCollapse = false to avoid recursively collapsing a hierarchy of submenus that each contain one item
|
|
FPopulateMenuBuilderWithToolMenuEntry PopulateMenuBuilderWithToolMenuEntry(MenuBuilder, MenuData, Section, *FirstEntry, /* bAllowSubMenuCollapse= */ false);
|
|
PopulateMenuBuilderWithToolMenuEntry.SetBlockNameOverride(Block.Name);
|
|
PopulateMenuBuilderWithToolMenuEntry.Populate();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
NewMenuDelegate = FNewMenuDelegate::CreateUObject(UToolMenus::Get(), &UToolMenus::PopulateSubMenu, TWeakObjectPtr<UToolMenu>(MenuData), Block, BlockNameOverride);
|
|
}
|
|
|
|
if (!bSubMenuAdded)
|
|
{
|
|
const FToolMenuVisibilityChoice VisibilityOverride = UE::ToolMenus::Private::CombineVisibility(Section.Visibility, Block.Visibility);
|
|
|
|
if (Widget.IsValid())
|
|
{
|
|
if (bUIActionIsSet)
|
|
{
|
|
MenuBuilder.AddSubMenu(
|
|
UIAction, Widget.ToSharedRef(), NewMenuDelegate, Block.bShouldCloseWindowAfterMenuSelection, VisibilityOverride
|
|
);
|
|
}
|
|
else
|
|
{
|
|
MenuBuilder.AddSubMenu(
|
|
Widget.ToSharedRef(),
|
|
NewMenuDelegate,
|
|
Block.SubMenuData.bOpenSubMenuOnClick,
|
|
Block.bShouldCloseWindowAfterMenuSelection,
|
|
VisibilityOverride
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (bUIActionIsSet)
|
|
{
|
|
MenuBuilder.AddSubMenu(
|
|
Block.Label,
|
|
Block.ToolTip,
|
|
NewMenuDelegate,
|
|
UIAction,
|
|
BlockNameOverride,
|
|
Block.UserInterfaceActionType,
|
|
Block.SubMenuData.bOpenSubMenuOnClick,
|
|
Block.Icon.Get(),
|
|
Block.bShouldCloseWindowAfterMenuSelection,
|
|
VisibilityOverride,
|
|
Block.InputBindingLabel
|
|
);
|
|
}
|
|
else
|
|
{
|
|
MenuBuilder.AddSubMenu(
|
|
Block.Label,
|
|
Block.ToolTip,
|
|
NewMenuDelegate,
|
|
Block.SubMenuData.bOpenSubMenuOnClick,
|
|
Block.Icon.Get(),
|
|
Block.bShouldCloseWindowAfterMenuSelection,
|
|
BlockNameOverride,
|
|
Block.TutorialHighlightName,
|
|
VisibilityOverride
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void AddStandardEntryToMenuBuilder()
|
|
{
|
|
const FToolMenuVisibilityChoice VisibilityOverride = UE::ToolMenus::Private::CombineVisibility(Section.Visibility, Block.Visibility);
|
|
|
|
// First, check for a ToolUIAction, otherwise do the rest of this (have CommandList and Command)
|
|
// Need another variable to store if we are using a keybind from a command
|
|
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[%s], menu: %s"),
|
|
*BlockNameOverride.ToString(),
|
|
**FTextInspector::GetSourceString(LabelToDisplay.Get()),
|
|
*MenuData->MenuName.ToString());
|
|
}
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
Block.Command, BlockNameOverride, LabelToDisplay, Block.ToolTip, Block.Icon.Get(), NAME_None, VisibilityOverride
|
|
);
|
|
|
|
if (bPopCommandList)
|
|
{
|
|
MenuBuilder.PopCommandList();
|
|
}
|
|
}
|
|
else if (Block.ScriptObject)
|
|
{
|
|
UToolMenuEntryScript* ScriptObject = Block.ScriptObject;
|
|
const FSlateIcon Icon = ScriptObject->CreateIconAttribute(MenuData->Context).Get();
|
|
|
|
FMenuEntryParams MenuEntryParams;
|
|
MenuEntryParams.LabelOverride = ScriptObject->CreateLabelAttribute(MenuData->Context);
|
|
MenuEntryParams.ToolTipOverride = ScriptObject->CreateToolTipAttribute(MenuData->Context);
|
|
MenuEntryParams.IconOverride = Icon;
|
|
MenuEntryParams.DirectActions = UIAction;
|
|
MenuEntryParams.ExtensionHook = ScriptObject->Data.Name;
|
|
MenuEntryParams.UserInterfaceActionType = Block.UserInterfaceActionType;
|
|
MenuEntryParams.TutorialHighlightName = Block.TutorialHighlightName;
|
|
MenuEntryParams.InputBindingOverride = Block.InputBindingLabel;
|
|
MenuEntryParams.Visibility = VisibilityOverride;
|
|
|
|
MenuBuilder.AddMenuEntry(MenuEntryParams);
|
|
}
|
|
else
|
|
{
|
|
if (Widget.IsValid())
|
|
{
|
|
FMenuEntryParams MenuEntryParams;
|
|
MenuEntryParams.DirectActions = UIAction;
|
|
MenuEntryParams.EntryWidget = Widget.ToSharedRef();
|
|
MenuEntryParams.ExtensionHook = BlockNameOverride;
|
|
MenuEntryParams.ToolTipOverride = Block.ToolTip;
|
|
MenuEntryParams.UserInterfaceActionType = Block.UserInterfaceActionType;
|
|
MenuEntryParams.TutorialHighlightName = Block.TutorialHighlightName;
|
|
MenuEntryParams.InputBindingOverride = Block.InputBindingLabel;
|
|
MenuEntryParams.Visibility = VisibilityOverride;
|
|
|
|
MenuBuilder.AddMenuEntry(MenuEntryParams);
|
|
}
|
|
else
|
|
{
|
|
MenuBuilder.AddMenuEntry(
|
|
LabelToDisplay,
|
|
Block.ToolTip,
|
|
Block.Icon.Get(),
|
|
UIAction,
|
|
BlockNameOverride,
|
|
Block.UserInterfaceActionType,
|
|
Block.TutorialHighlightName,
|
|
Block.InputBindingLabel,
|
|
VisibilityOverride
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Populate()
|
|
{
|
|
if (Block.ConstructLegacy.IsBound())
|
|
{
|
|
if (!bIsEditing)
|
|
{
|
|
Block.ConstructLegacy.Execute(MenuBuilder, MenuData);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
const FToolMenuVisibilityChoice VisibilityOverride = UE::ToolMenus::Private::CombineVisibility(Section.Visibility, Block.Visibility);
|
|
|
|
UIAction = UToolMenus::ConvertUIAction(Block, MenuData->Context);
|
|
bUIActionIsSet = UIAction.ExecuteAction.IsBound() || UIAction.CanExecuteAction.IsBound() || UIAction.GetActionCheckState.IsBound() || UIAction.IsActionVisibleDelegate.IsBound();
|
|
|
|
if (Block.MakeCustomWidget.IsBound())
|
|
{
|
|
FToolMenuCustomWidgetContext EntryWidgetContext;
|
|
TSharedRef<FMultiBox> MultiBox = MenuBuilder.GetMultiBox();
|
|
EntryWidgetContext.StyleSet = MultiBox->GetStyleSet();
|
|
EntryWidgetContext.StyleName = MultiBox->GetStyleName();
|
|
Widget = Block.MakeCustomWidget.Execute(MenuData->Context, EntryWidgetContext);
|
|
}
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
else if (Block.MakeWidget.IsBound())
|
|
{
|
|
Widget = Block.MakeWidget.Execute(MenuData->Context);
|
|
}
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
|
|
LabelToDisplay = Block.Label;
|
|
if (bIsEditing && (!Block.Label.IsSet() || Block.Label.Get().IsEmpty()))
|
|
{
|
|
LabelToDisplay = FText::FromName(BlockNameOverride);
|
|
}
|
|
|
|
if (Block.Type == EMultiBlockType::MenuEntry)
|
|
{
|
|
if (Block.IsSubMenu())
|
|
{
|
|
AddSubMenuEntryToMenuBuilder();
|
|
}
|
|
else
|
|
{
|
|
AddStandardEntryToMenuBuilder();
|
|
}
|
|
}
|
|
else if (Block.Type == EMultiBlockType::Separator)
|
|
{
|
|
MenuBuilder.AddSeparator(BlockNameOverride, VisibilityOverride);
|
|
}
|
|
else if (Block.Type == EMultiBlockType::Widget)
|
|
{
|
|
if (bIsEditing)
|
|
{
|
|
FMenuEntryParams MenuEntryParams;
|
|
MenuEntryParams.LabelOverride = LabelToDisplay;
|
|
MenuEntryParams.ToolTipOverride = Block.ToolTip;
|
|
MenuEntryParams.IconOverride = Block.Icon.Get();
|
|
MenuEntryParams.DirectActions = UIAction;
|
|
MenuEntryParams.ExtensionHook = BlockNameOverride;
|
|
MenuEntryParams.UserInterfaceActionType = Block.UserInterfaceActionType;
|
|
MenuEntryParams.TutorialHighlightName = Block.TutorialHighlightName;
|
|
MenuEntryParams.InputBindingOverride = Block.InputBindingLabel;
|
|
MenuEntryParams.Visibility = VisibilityOverride;
|
|
|
|
MenuBuilder.AddMenuEntry(MenuEntryParams);
|
|
}
|
|
else
|
|
{
|
|
Block.WidgetData.StyleParams.bNoIndent = Block.WidgetData.bNoIndent;
|
|
MenuBuilder.AddWidget(
|
|
Widget.ToSharedRef(),
|
|
LabelToDisplay.Get(),
|
|
Block.WidgetData.StyleParams,
|
|
Block.WidgetData.ResizeParams,
|
|
Block.WidgetData.bSearchable,
|
|
Block.ToolTip.Get(),
|
|
Block.Icon,
|
|
VisibilityOverride
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogToolMenus, Warning, TEXT("Menu '%s', item '%s', Menus do not support: %s"), *MenuData->MenuName.ToString(), *BlockNameOverride.ToString(), *UEnum::GetValueAsString(Block.Type));
|
|
}
|
|
};
|
|
|
|
void SetBlockNameOverride(const FName InBlockNameOverride) { BlockNameOverride = InBlockNameOverride; };
|
|
|
|
private:
|
|
FMenuBuilder& MenuBuilder;
|
|
UToolMenu* MenuData;
|
|
FToolMenuSection& Section;
|
|
FToolMenuEntry& Block;
|
|
FName BlockNameOverride;
|
|
|
|
FUIAction UIAction;
|
|
TSharedPtr<SWidget> Widget;
|
|
TAttribute<FText> LabelToDisplay;
|
|
bool bAllowSubMenuCollapse;
|
|
bool bUIActionIsSet;
|
|
const bool bIsEditing;
|
|
};
|
|
|
|
UToolMenus::UToolMenus() :
|
|
bNextTickTimerIsSet(false),
|
|
bRefreshWidgetsNextTick(false),
|
|
bCleanupStaleWidgetsNextTick(false),
|
|
bCleanupStaleWidgetsNextTickGC(false),
|
|
bEditMenusMode(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 = UE::ToolMenus::Private::CreateToolMenusInstance();
|
|
}
|
|
|
|
return Singleton;
|
|
}
|
|
|
|
void UToolMenus::BeginDestroy()
|
|
{
|
|
if (Singleton == this)
|
|
{
|
|
UnregisterPrivateStartupCallback();
|
|
|
|
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::SplitMenuPath(const FName MenuPath, FName& OutLeft, FName& OutRight)
|
|
{
|
|
if (MenuPath != NAME_None)
|
|
{
|
|
FString Left;
|
|
FString Right;
|
|
if (MenuPath.ToString().Split(TEXT("."), &Left, &Right, ESearchCase::CaseSensitive, ESearchDir::FromEnd))
|
|
{
|
|
OutLeft = *Left;
|
|
OutRight = *Right;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool UToolMenus::GetDisplayUIExtensionPoints() const
|
|
{
|
|
return ShouldDisplayExtensionPoints.IsBound() && ShouldDisplayExtensionPoints.Execute();
|
|
}
|
|
|
|
UToolMenu* UToolMenus::FindMenu(const FName Name)
|
|
{
|
|
TObjectPtr<UToolMenu>* Found = Menus.Find(Name);
|
|
return Found ? *Found : nullptr;
|
|
}
|
|
|
|
bool UToolMenus::IsMenuRegistered(const FName Name) const
|
|
{
|
|
TObjectPtr<UToolMenu> const * Found = Menus.Find(Name);
|
|
return Found && *Found && (*Found)->IsRegistered();
|
|
}
|
|
|
|
TArray<UToolMenu*> UToolMenus::CollectHierarchy(const FName InName, const TMap<FName, FName>& UnregisteredParentNames)
|
|
{
|
|
TArray<UToolMenu*> Result;
|
|
TArray<FName> SubstitutedMenuNames;
|
|
|
|
FName CurrentMenuName = InName;
|
|
while (CurrentMenuName != NAME_None)
|
|
{
|
|
FName AdjustedMenuName = CurrentMenuName;
|
|
if (!SubstitutedMenuNames.Contains(AdjustedMenuName))
|
|
{
|
|
if (const FName* SubstitutionName = MenuSubstitutionsDuringGenerate.Find(CurrentMenuName))
|
|
{
|
|
// Allow collection hierarchy when InName is a substitute for one of InName's parents
|
|
// Will occur in menu editor when a substitute menu is selected from drop down list
|
|
bool bSubstituteAlreadyInHierarchy = false;
|
|
for (const UToolMenu* Other : Result)
|
|
{
|
|
if (Other->GetMenuName() == *SubstitutionName)
|
|
{
|
|
bSubstituteAlreadyInHierarchy = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!bSubstituteAlreadyInHierarchy)
|
|
{
|
|
AdjustedMenuName = *SubstitutionName;
|
|
|
|
// Handle substitute's parent hierarchy including the original menu again by not substituting the same menu twice
|
|
SubstitutedMenuNames.Add(CurrentMenuName);
|
|
}
|
|
}
|
|
}
|
|
|
|
UToolMenu* Current = FindMenu(AdjustedMenuName);
|
|
if (!Current)
|
|
{
|
|
UE_LOG(LogToolMenus, Warning, TEXT("Failed to find menu: %s for %s"), *AdjustedMenuName.ToString(), *InName.ToString());
|
|
return TArray<UToolMenu*>();
|
|
}
|
|
|
|
if (Result.Contains(Current))
|
|
{
|
|
UE_LOG(LogToolMenus, Warning, TEXT("Infinite loop detected in tool menu: %s"), *InName.ToString());
|
|
return TArray<UToolMenu*>();
|
|
}
|
|
|
|
Result.Add(Current);
|
|
|
|
if (Current->IsRegistered())
|
|
{
|
|
CurrentMenuName = Current->MenuParent;
|
|
}
|
|
else if (const FName* FoundUnregisteredParentName = UnregisteredParentNames.Find(CurrentMenuName))
|
|
{
|
|
CurrentMenuName = *FoundUnregisteredParentName;
|
|
}
|
|
else
|
|
{
|
|
CurrentMenuName = NAME_None;
|
|
}
|
|
}
|
|
|
|
Algo::Reverse(Result);
|
|
|
|
return Result;
|
|
}
|
|
|
|
void UToolMenus::ApplyStyleToBuilder(FMenuBuilder& InBuilder, const FToolMenuEntryStyle& InStyle)
|
|
{
|
|
const ISlateStyle* StyleSet = InBuilder.GetStyleSet();
|
|
|
|
// If specified, the provided StyleSet always overrides that specified in the builder
|
|
if (InStyle.StyleSet.IsSet())
|
|
{
|
|
StyleSet = InStyle.StyleSet.GetValue();
|
|
}
|
|
|
|
InBuilder.SetStyle(StyleSet, InStyle.StyleName.Get(InBuilder.GetStyleName()));
|
|
}
|
|
|
|
TArray<UToolMenu*> UToolMenus::CollectHierarchy(const FName InName)
|
|
{
|
|
TMap<FName, FName> UnregisteredParents;
|
|
return CollectHierarchy(InName, UnregisteredParents);
|
|
}
|
|
|
|
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)
|
|
{
|
|
if (!DestSection)
|
|
{
|
|
UE_LOG(LogToolMenus, Warning, TEXT("Trying to add to invalid section for menu: %s, section: %s. Default section info will be used instead."), *OtherSection.Owner.TryGetName().ToString(), *OtherSection.Name.ToString());
|
|
}
|
|
// Build list of blocks in expected order including blocks created by construct delegates
|
|
TArray<FToolMenuEntry> RemainingBlocks;
|
|
TArray<FToolMenuEntry> BlocksToAddLast;
|
|
|
|
UToolMenu* ConstructedEntries = nullptr;
|
|
for (const FToolMenuEntry& Block : OtherSection.Blocks)
|
|
{
|
|
if (!Block.IsNonLegacyDynamicConstruct())
|
|
{
|
|
if (Block.bAddedDuringRegister)
|
|
{
|
|
RemainingBlocks.Add(Block);
|
|
}
|
|
else
|
|
{
|
|
BlocksToAddLast.Add(Block);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (ConstructedEntries == nullptr)
|
|
{
|
|
ConstructedEntries = NewToolMenuObject(FName(TEXT("TempAssembleMenuSection")), NAME_None);
|
|
if (!ensure(ConstructedEntries))
|
|
{
|
|
break;
|
|
}
|
|
if (DestSection)
|
|
{
|
|
ConstructedEntries->Context = DestSection->Context;
|
|
}
|
|
else
|
|
{
|
|
ConstructedEntries->Context = FToolMenuContext();
|
|
}
|
|
}
|
|
|
|
TArray<FToolMenuEntry> GeneratedEntries;
|
|
GeneratedEntries.Add(Block);
|
|
|
|
int32 NumIterations = 0;
|
|
while (GeneratedEntries.Num() > 0)
|
|
{
|
|
FToolMenuEntry& GeneratedEntry = GeneratedEntries[0];
|
|
if (GeneratedEntry.IsNonLegacyDynamicConstruct())
|
|
{
|
|
if (NumIterations++ > 5000)
|
|
{
|
|
FName MenuName = OtherSection.Owner.TryGetName();
|
|
|
|
if (Other)
|
|
{
|
|
MenuName = Other->MenuName;
|
|
}
|
|
UE_LOG(LogToolMenus, Warning, TEXT("Possible infinite loop for menu: %s, section: %s, block: %s"), *MenuName.ToString(), *OtherSection.Name.ToString(), *Block.Name.ToString());
|
|
break;
|
|
}
|
|
|
|
ConstructedEntries->Sections.Reset();
|
|
if (GeneratedEntry.IsScriptObjectDynamicConstruct())
|
|
{
|
|
FName SectionName;
|
|
FToolMenuContext SectionContext;
|
|
if (DestSection)
|
|
{
|
|
SectionName = DestSection->Name;
|
|
SectionContext = DestSection->Context;
|
|
}
|
|
GeneratedEntry.ScriptObject->ConstructMenuEntry(ConstructedEntries, SectionName, SectionContext);
|
|
}
|
|
else
|
|
{
|
|
FName SectionName;
|
|
if (DestSection)
|
|
{
|
|
SectionName = DestSection->Name;
|
|
}
|
|
FToolMenuSection& ConstructedSection = ConstructedEntries->AddSection(SectionName);
|
|
ConstructedSection.Context = ConstructedEntries->Context;
|
|
GeneratedEntry.Construct.Execute(ConstructedSection);
|
|
}
|
|
GeneratedEntries.RemoveAt(0, EAllowShrinking::No);
|
|
|
|
// 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
|
|
{
|
|
if (Block.bAddedDuringRegister)
|
|
{
|
|
RemainingBlocks.Add(GeneratedEntry);
|
|
}
|
|
else
|
|
{
|
|
BlocksToAddLast.Add(GeneratedEntry);
|
|
}
|
|
GeneratedEntries.RemoveAt(0, EAllowShrinking::No);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ConstructedEntries)
|
|
{
|
|
ConstructedEntries->Empty();
|
|
ConstructedEntries = nullptr;
|
|
}
|
|
|
|
RemainingBlocks.Append(BlocksToAddLast);
|
|
|
|
// Only do this loop if there is a section to insert into. We need to early-out here or it will be an infinite loop
|
|
if (DestSection)
|
|
{
|
|
// 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 = NewToolMenuObject(FName(TEXT("TempAssembleMenu")), NAME_None);
|
|
if (!ensure(ConstructedSections))
|
|
{
|
|
break;
|
|
}
|
|
ConstructedSections->Context = GeneratedMenu->Context;
|
|
ConstructedSections->MenuType = GeneratedMenu->MenuType;
|
|
}
|
|
|
|
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, EAllowShrinking::No);
|
|
GeneratedSections.Insert(ConstructedSections->Sections, 0);
|
|
}
|
|
else
|
|
{
|
|
RemainingSections.Add(GeneratedSections[0]);
|
|
GeneratedSections.RemoveAt(0, EAllowShrinking::No);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ConstructedSections)
|
|
{
|
|
ConstructedSections->Empty();
|
|
ConstructedSections = nullptr;
|
|
}
|
|
|
|
while (RemainingSections.Num() > 0)
|
|
{
|
|
int32 NumHandled = 0;
|
|
for (int32 i=0; i < RemainingSections.Num(); ++i)
|
|
{
|
|
FToolMenuSection& RemainingSection = RemainingSections[i];
|
|
|
|
// Menubars do not have sections, combine all sections into one
|
|
if (GeneratedMenu->MenuType == EMultiBoxType::MenuBar)
|
|
{
|
|
RemainingSection.Name = NAME_None;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// Let child menu override dynamic legacy section
|
|
if (!RemainingSection.IsNonLegacyDynamic())
|
|
{
|
|
Section->Construct = RemainingSection.Construct;
|
|
}
|
|
|
|
if (!Section->Visibility.IsSet() && RemainingSection.Visibility.IsSet())
|
|
{
|
|
Section->Visibility = RemainingSection.Visibility;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool UToolMenus::GetEditMenusMode() const
|
|
{
|
|
return bEditMenusMode;
|
|
}
|
|
|
|
void UToolMenus::SetEditMenusMode(bool bShow)
|
|
{
|
|
if (bEditMenusMode != bShow)
|
|
{
|
|
bEditMenusMode = bShow;
|
|
RefreshAllWidgets();
|
|
}
|
|
}
|
|
|
|
void UToolMenus::RemoveCustomization(const FName InName)
|
|
{
|
|
int32 FoundIndex = FindMenuCustomizationIndex(InName);
|
|
if (FoundIndex != INDEX_NONE)
|
|
{
|
|
CustomizedMenus.RemoveAt(FoundIndex, EAllowShrinking::No);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
FCustomizedToolMenu* UToolMenus::FindRuntimeMenuCustomization(const FName InName)
|
|
{
|
|
for (int32 i = 0; i < RuntimeCustomizedMenus.Num(); ++i)
|
|
{
|
|
if (RuntimeCustomizedMenus[i].Name == InName)
|
|
{
|
|
return &RuntimeCustomizedMenus[i];
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
FCustomizedToolMenu* UToolMenus::AddRuntimeMenuCustomization(const FName InName)
|
|
{
|
|
if (FCustomizedToolMenu* Found = FindRuntimeMenuCustomization(InName))
|
|
{
|
|
return Found;
|
|
}
|
|
else
|
|
{
|
|
FCustomizedToolMenu& NewCustomization = RuntimeCustomizedMenus.AddDefaulted_GetRef();
|
|
NewCustomization.Name = InName;
|
|
return &NewCustomization;
|
|
}
|
|
}
|
|
|
|
FToolMenuProfile* UToolMenus::FindMenuProfile(const FName InMenuName, const FName InProfileName)
|
|
{
|
|
if(FToolMenuProfileMap* FoundMenu = MenuProfiles.Find(InMenuName))
|
|
{
|
|
return FoundMenu->MenuProfiles.Find(InProfileName);
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
FToolMenuProfile* UToolMenus::AddMenuProfile(const FName InMenuName, const FName InProfileName)
|
|
{
|
|
if (FToolMenuProfile* Found = FindMenuProfile(InMenuName, InProfileName))
|
|
{
|
|
return Found;
|
|
}
|
|
else
|
|
{
|
|
FToolMenuProfileMap& FoundMenu = MenuProfiles.FindOrAdd(InMenuName);
|
|
|
|
FToolMenuProfile& NewCustomization = FoundMenu.MenuProfiles.Add(InProfileName, FToolMenuProfile());
|
|
NewCustomization.Name = InProfileName;
|
|
return &NewCustomization;
|
|
}
|
|
}
|
|
|
|
|
|
FToolMenuProfile* UToolMenus::FindRuntimeMenuProfile(const FName InMenuName, const FName InProfileName)
|
|
{
|
|
if(FToolMenuProfileMap* FoundMenu = RuntimeMenuProfiles.Find(InMenuName))
|
|
{
|
|
return FoundMenu->MenuProfiles.Find(InProfileName);
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
FToolMenuProfile* UToolMenus::AddRuntimeMenuProfile(const FName InMenuName, const FName InProfileName)
|
|
{
|
|
if (FToolMenuProfile* Found = FindRuntimeMenuProfile(InMenuName, InProfileName))
|
|
{
|
|
return Found;
|
|
}
|
|
else
|
|
{
|
|
FToolMenuProfileMap& FoundMenu = RuntimeMenuProfiles.FindOrAdd(InMenuName);
|
|
|
|
FToolMenuProfile& NewCustomization = FoundMenu.MenuProfiles.Add(InProfileName, FToolMenuProfile());
|
|
NewCustomization.Name = InProfileName;
|
|
return &NewCustomization;
|
|
}
|
|
}
|
|
|
|
void UToolMenus::ApplyCustomizationAndProfiles(UToolMenu* GeneratedMenu)
|
|
{
|
|
// Apply all profiles that are active by looking for them in the context
|
|
UToolMenuProfileContext* ProfileContext = GeneratedMenu->FindContext<UToolMenuProfileContext>();
|
|
|
|
if(ProfileContext)
|
|
{
|
|
for(const FName& ActiveProfile : ProfileContext->ActiveProfiles)
|
|
{
|
|
FToolMenuProfileHierarchy MenuProfileHieararchy = GeneratedMenu->GetMenuProfileHierarchy(ActiveProfile);
|
|
|
|
if (MenuProfileHieararchy.ProfileHierarchy.Num() != 0 || MenuProfileHieararchy.RuntimeProfileHierarchy.Num() != 0)
|
|
{
|
|
FToolMenuProfile MenuProfile = MenuProfileHieararchy.GenerateFlattenedMenuProfile();
|
|
ApplyProfile(GeneratedMenu, MenuProfile);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogToolMenus, Verbose, TEXT("Menu Profile %s for menu %s not found!"), *ActiveProfile.ToString(), *GeneratedMenu->GetMenuName().ToString());
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
// Apply the customization for the menu (if any)
|
|
FCustomizedToolMenuHierarchy CustomizationHierarchy = GeneratedMenu->GetMenuCustomizationHierarchy();
|
|
if (CustomizationHierarchy.Hierarchy.Num() != 0 || CustomizationHierarchy.RuntimeHierarchy.Num() != 0)
|
|
{
|
|
FCustomizedToolMenu CustomizedMenu = CustomizationHierarchy.GenerateFlattened();
|
|
ApplyCustomization(GeneratedMenu, CustomizedMenu);
|
|
}
|
|
}
|
|
|
|
void UToolMenus::ApplyProfile(UToolMenu* GeneratedMenu, const FToolMenuProfile& MenuProfile)
|
|
{
|
|
if (MenuProfile.IsSuppressExtenders())
|
|
{
|
|
GeneratedMenu->SetExtendersEnabled(false);
|
|
}
|
|
|
|
TArray<FToolMenuSection> NewSections(GeneratedMenu->Sections);
|
|
|
|
// Hide items based on deny list
|
|
if (MenuProfile.MenuPermissions.HasFiltering())
|
|
{
|
|
for (int32 SectionIndex = 0; SectionIndex < NewSections.Num(); ++SectionIndex)
|
|
{
|
|
FToolMenuSection& Section = NewSections[SectionIndex];
|
|
for (int32 i = 0; i < Section.Blocks.Num(); ++i)
|
|
{
|
|
if (!MenuProfile.MenuPermissions.PassesFilter(Section.Blocks[i].Name))
|
|
{
|
|
Section.Blocks.RemoveAt(i);
|
|
--i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Hide sections and entries
|
|
if (!GeneratedMenu->IsEditing())
|
|
{
|
|
for (int32 SectionIndex = 0; SectionIndex < NewSections.Num(); ++SectionIndex)
|
|
{
|
|
FToolMenuSection& Section = NewSections[SectionIndex];
|
|
if (MenuProfile.IsSectionHidden(Section.Name))
|
|
{
|
|
NewSections.RemoveAt(SectionIndex);
|
|
--SectionIndex;
|
|
continue;
|
|
}
|
|
|
|
for (int32 i = 0; i < Section.Blocks.Num(); ++i)
|
|
{
|
|
if (MenuProfile.IsEntryHidden(Section.Blocks[i].Name))
|
|
{
|
|
Section.Blocks.RemoveAt(i);
|
|
--i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
GeneratedMenu->Sections = NewSections;
|
|
|
|
}
|
|
|
|
void UToolMenus::ApplyCustomization(UToolMenu* GeneratedMenu, const FCustomizedToolMenu& CustomizedMenu)
|
|
{
|
|
TArray<FToolMenuSection> NewSections;
|
|
NewSections.Reserve(GeneratedMenu->Sections.Num());
|
|
|
|
TSet<FName> PlacedEntries;
|
|
|
|
TArray<int32> NewSectionIndices;
|
|
NewSectionIndices.Reserve(GeneratedMenu->Sections.Num());
|
|
|
|
// Add sections with customized ordering first
|
|
for (const FName& SectionName : CustomizedMenu.SectionOrder)
|
|
{
|
|
if (SectionName != NAME_None)
|
|
{
|
|
int32 OriginalIndex = GeneratedMenu->Sections.IndexOfByPredicate([SectionName](const FToolMenuSection& OriginalSection) { return OriginalSection.Name == SectionName; });
|
|
if (OriginalIndex != INDEX_NONE)
|
|
{
|
|
NewSectionIndices.Add(OriginalIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remaining sections get added to the end
|
|
for (int32 i = 0; i < GeneratedMenu->Sections.Num(); ++i)
|
|
{
|
|
NewSectionIndices.AddUnique(i);
|
|
}
|
|
|
|
// Copy sections
|
|
for (int32 i = 0; i < NewSectionIndices.Num(); ++i)
|
|
{
|
|
FToolMenuSection& NewSection = NewSections.Add_GetRef(GeneratedMenu->Sections[NewSectionIndices[i]]);
|
|
NewSection.Blocks.Reset();
|
|
}
|
|
|
|
// Add entries placed by customization
|
|
for (int32 i = 0; i < NewSectionIndices.Num(); ++i)
|
|
{
|
|
const FToolMenuSection& OriginalSection = GeneratedMenu->Sections[NewSectionIndices[i]];
|
|
FToolMenuSection& NewSection = NewSections[i];
|
|
|
|
if (OriginalSection.Name != NAME_None)
|
|
{
|
|
if (const FCustomizedToolMenuNameArray* EntryOrder = CustomizedMenu.EntryOrder.Find(OriginalSection.Name))
|
|
{
|
|
for (const FName& EntryName : EntryOrder->Names)
|
|
{
|
|
if (EntryName != NAME_None)
|
|
{
|
|
if (FToolMenuEntry* SourceEntry = GeneratedMenu->FindEntry(EntryName))
|
|
{
|
|
NewSection.Blocks.Add(*SourceEntry);
|
|
PlacedEntries.Add(EntryName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle entries not placed by customization
|
|
for (int32 i = 0; i < NewSectionIndices.Num(); ++i)
|
|
{
|
|
const FToolMenuSection& OriginalSection = GeneratedMenu->Sections[NewSectionIndices[i]];
|
|
FToolMenuSection& NewSection = NewSections[i];
|
|
|
|
for (const FToolMenuEntry& OriginalEntry : OriginalSection.Blocks)
|
|
{
|
|
if (OriginalEntry.Name == NAME_None)
|
|
{
|
|
NewSection.Blocks.Add(OriginalEntry);
|
|
}
|
|
else
|
|
{
|
|
bool bAlreadyPlaced = false;
|
|
PlacedEntries.Add(OriginalEntry.Name, &bAlreadyPlaced);
|
|
if (!bAlreadyPlaced)
|
|
{
|
|
NewSection.Blocks.Add(OriginalEntry);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
GeneratedMenu->Sections = NewSections;
|
|
|
|
ApplyProfile(GeneratedMenu, CustomizedMenu);
|
|
}
|
|
|
|
void UToolMenus::AssembleMenuHierarchy(UToolMenu* GeneratedMenu, const TArray<UToolMenu*>& Hierarchy)
|
|
{
|
|
TGuardValue<bool> SuppressRefreshWidgetsRequestsGuard(bSuppressRefreshWidgetsRequests, true);
|
|
|
|
for (const UToolMenu* FoundParent : Hierarchy)
|
|
{
|
|
AssembleMenu(GeneratedMenu, FoundParent);
|
|
}
|
|
|
|
for (FToolMenuSection& Section : GeneratedMenu->Sections)
|
|
{
|
|
if (Section.Sorter.IsBound())
|
|
{
|
|
Section.Blocks.StableSort([&Section](const FToolMenuEntry& A, const FToolMenuEntry& B)
|
|
{
|
|
return Section.Sorter.Execute(A, B, Section.Context);
|
|
});
|
|
}
|
|
}
|
|
|
|
ApplyCustomizationAndProfiles(GeneratedMenu);
|
|
}
|
|
|
|
UToolMenu* UToolMenus::GenerateSubMenu(const UToolMenu* InGeneratedParent, const FName InBlockName)
|
|
{
|
|
if (InGeneratedParent == nullptr || InBlockName == NAME_None)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
FName SubMenuFullName = JoinMenuPaths(InGeneratedParent->GetMenuName(), InBlockName);
|
|
|
|
const FToolMenuEntry* Block = InGeneratedParent->FindEntry(InBlockName);
|
|
if (!Block)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
TGuardValue<bool> SuppressRefreshWidgetsRequestsGuard(bSuppressRefreshWidgetsRequests, true);
|
|
|
|
// Submenus that are constructed by delegates can also be overridden by menus in the database
|
|
TArray<UToolMenu*> Hierarchy;
|
|
{
|
|
struct FMenuHierarchyInfo
|
|
{
|
|
FMenuHierarchyInfo() : BaseMenu(nullptr), SubMenu(nullptr) { }
|
|
FName BaseMenuName;
|
|
FName SubMenuName;
|
|
UToolMenu* BaseMenu;
|
|
UToolMenu* SubMenu;
|
|
};
|
|
|
|
TArray<FMenuHierarchyInfo> HierarchyInfos;
|
|
TArray<UToolMenu*> UnregisteredHierarchy;
|
|
|
|
// Walk up all parent menus trying to find a menu
|
|
FName BaseName = InGeneratedParent->GetMenuName();
|
|
while (BaseName != NAME_None)
|
|
{
|
|
FMenuHierarchyInfo& Info = HierarchyInfos.AddDefaulted_GetRef();
|
|
Info.BaseMenuName = BaseName;
|
|
Info.BaseMenu = FindMenu(Info.BaseMenuName);
|
|
Info.SubMenuName = JoinMenuPaths(BaseName, InBlockName);
|
|
Info.SubMenu = FindMenu(Info.SubMenuName);
|
|
|
|
if (Info.SubMenu)
|
|
{
|
|
if (Info.SubMenu->IsRegistered())
|
|
{
|
|
if (UnregisteredHierarchy.Num() == 0)
|
|
{
|
|
Hierarchy = CollectHierarchy(Info.SubMenuName);
|
|
}
|
|
else
|
|
{
|
|
UnregisteredHierarchy.Add(Info.SubMenu);
|
|
}
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
UnregisteredHierarchy.Add(Info.SubMenu);
|
|
}
|
|
}
|
|
|
|
BaseName = Info.BaseMenu ? Info.BaseMenu->MenuParent : NAME_None;
|
|
}
|
|
|
|
if (UnregisteredHierarchy.Num() > 0)
|
|
{
|
|
// Create lookup for UToolMenus that were extended but not registered
|
|
TMap<FName, FName> UnregisteredParentNames;
|
|
for (int32 i = 0; i < UnregisteredHierarchy.Num() - 1; ++i)
|
|
{
|
|
UnregisteredParentNames.Add(UnregisteredHierarchy[i]->GetMenuName(), UnregisteredHierarchy[i + 1]->GetMenuName());
|
|
}
|
|
Hierarchy = CollectHierarchy(UnregisteredHierarchy[0]->GetMenuName(), UnregisteredParentNames);
|
|
}
|
|
}
|
|
|
|
// Construct menu using delegate and insert as root so it can be overridden
|
|
TArray<UToolMenu*> MenusToCleanup;
|
|
if (Block->SubMenuData.ConstructMenu.NewToolMenu.IsBound())
|
|
{
|
|
UToolMenu* Menu = NewToolMenuObject(FName(TEXT("TempGenerateSubMenu")), SubMenuFullName);
|
|
MenusToCleanup.Add(Menu);
|
|
Menu->Context = InGeneratedParent->Context;
|
|
|
|
// Submenu specific data
|
|
Menu->SubMenuParent = InGeneratedParent;
|
|
Menu->SubMenuSourceEntryName = InBlockName;
|
|
|
|
Block->SubMenuData.ConstructMenu.NewToolMenu.Execute(Menu);
|
|
Menu->MenuName = SubMenuFullName;
|
|
Hierarchy.Insert(Menu, 0);
|
|
}
|
|
|
|
// Populate menu builder with final menu
|
|
if (Hierarchy.Num() > 0)
|
|
{
|
|
UToolMenu* GeneratedMenu = NewToolMenuObject(FName(TEXT("GeneratedSubMenu")), SubMenuFullName);
|
|
GeneratedMenu->InitGeneratedCopy(Hierarchy[0], SubMenuFullName, &InGeneratedParent->Context);
|
|
for (UToolMenu* HiearchyItem : Hierarchy)
|
|
{
|
|
if (HiearchyItem && !HiearchyItem->bExtendersEnabled)
|
|
{
|
|
GeneratedMenu->SetExtendersEnabled(false);
|
|
break;
|
|
}
|
|
}
|
|
GeneratedMenu->SubMenuParent = InGeneratedParent;
|
|
GeneratedMenu->SubMenuSourceEntryName = InBlockName;
|
|
AssembleMenuHierarchy(GeneratedMenu, Hierarchy);
|
|
for (UToolMenu* MenuToCleanup : MenusToCleanup)
|
|
{
|
|
MenuToCleanup->Empty();
|
|
}
|
|
MenusToCleanup.Empty();
|
|
return GeneratedMenu;
|
|
}
|
|
|
|
for (UToolMenu* MenuToCleanup : MenusToCleanup)
|
|
{
|
|
MenuToCleanup->Empty();
|
|
}
|
|
MenusToCleanup.Empty();
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void UToolMenus::PopulateSubMenu(FMenuBuilder& MenuBuilder, TWeakObjectPtr<UToolMenu> InParent, const FToolMenuEntry InEntry, const FName InBlockName)
|
|
{
|
|
if (UToolMenu* GeneratedMenu = GenerateSubMenu(InParent.Get(), InBlockName))
|
|
{
|
|
MenuBuilder.SetExtendersEnabled(GeneratedMenu->bExtendersEnabled);
|
|
|
|
// Apply Style override
|
|
ApplyStyleToBuilder(MenuBuilder, InEntry.SubMenuData.Style);
|
|
|
|
PopulateMenuBuilder(MenuBuilder, GeneratedMenu);
|
|
}
|
|
}
|
|
|
|
void UToolMenus::PopulateSubMenuWithoutName(FMenuBuilder& MenuBuilder, TWeakObjectPtr<UToolMenu> InParent, const FToolMenuEntry InEntry)
|
|
{
|
|
const UToolMenu* InGeneratedParent = InParent.Get();
|
|
if (InGeneratedParent == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (InEntry.SubMenuData.ConstructMenu.NewToolMenu.IsBound())
|
|
{
|
|
UToolMenu* Menu = NewToolMenuObject(FName(TEXT("SubMenuWithoutName")), NAME_None); // Menu does not have a name
|
|
Menu->Context = InGeneratedParent->Context;
|
|
|
|
// Submenu specific data
|
|
Menu->SubMenuParent = InGeneratedParent;
|
|
Menu->SubMenuSourceEntryName = NAME_None; // Entry does not have a name
|
|
|
|
// Apply Style override
|
|
ApplyStyleToBuilder(MenuBuilder, InEntry.SubMenuData.Style);
|
|
|
|
InEntry.SubMenuData.ConstructMenu.NewToolMenu.Execute(Menu);
|
|
Menu->MenuName = NAME_None; // Menu does not have a name
|
|
|
|
PopulateMenuBuilder(MenuBuilder, Menu);
|
|
}
|
|
}
|
|
|
|
TSharedRef<SWidget> UToolMenus::GenerateToolbarComboButtonMenu(TWeakObjectPtr<UToolMenu> InParent, const FName InBlockName)
|
|
{
|
|
if (UToolMenu* GeneratedMenu = GenerateSubMenu(InParent.Get(), InBlockName))
|
|
{
|
|
return GenerateWidget(GeneratedMenu);
|
|
}
|
|
|
|
return SNullWidget::NullWidget;
|
|
}
|
|
|
|
void UToolMenus::PopulateMenuBuilder(FMenuBuilder& MenuBuilder, UToolMenu* MenuData)
|
|
{
|
|
MenuBuilder.SetSearchable(MenuData->bSearchable);
|
|
|
|
const bool bIsEditing = MenuData->IsEditing();
|
|
if (GetEditMenusMode() && !bIsEditing && EditMenuDelegate.IsBound())
|
|
{
|
|
TWeakObjectPtr<UToolMenu> WeakMenuPtr = MenuData;
|
|
const FName MenuName = MenuData->GetMenuName();
|
|
MenuBuilder.AddMenuEntry(
|
|
FText::Format(LOCTEXT("EditMenu_Label", "Edit Menu: {0}"), FText::FromName(MenuName)),
|
|
LOCTEXT("EditMenu_ToolTip", "Open menu editor"),
|
|
EditMenuIcon,
|
|
FExecuteAction::CreateLambda([MenuName, WeakMenuPtr]()
|
|
{
|
|
FPlatformApplicationMisc::ClipboardCopy(*MenuName.ToString());
|
|
if (UToolMenu* InMenu = WeakMenuPtr.Get())
|
|
{
|
|
UToolMenus::Get()->EditMenuDelegate.ExecuteIfBound(InMenu);
|
|
}
|
|
}),
|
|
"MenuName"
|
|
);
|
|
}
|
|
|
|
for (int i=0; i < MenuData->Sections.Num(); ++i)
|
|
{
|
|
FToolMenuSection& Section = MenuData->Sections[i];
|
|
if (Section.Construct.NewToolMenuDelegateLegacy.IsBound())
|
|
{
|
|
if (!bIsEditing)
|
|
{
|
|
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, Section.Visibility, Section.ResizeParams);
|
|
}
|
|
else
|
|
{
|
|
MenuBuilder.BeginSection(Section.Name, Section.Label, Section.Visibility, Section.ResizeParams);
|
|
}
|
|
|
|
for (FToolMenuEntry& Block : Section.Blocks)
|
|
{
|
|
FPopulateMenuBuilderWithToolMenuEntry PopulateMenuBuilderWithToolMenuEntry(MenuBuilder, MenuData, Section, Block, /* bAllowSubMenuCollapse= */ true);
|
|
PopulateMenuBuilderWithToolMenuEntry.Populate();
|
|
}
|
|
|
|
MenuBuilder.EndSection();
|
|
}
|
|
|
|
MenuBuilder.GetMultiBox()->WeakToolMenu = MenuData;
|
|
AddReferencedContextObjects(MenuBuilder.GetMultiBox(), MenuData);
|
|
}
|
|
|
|
void UToolMenus::ExtractChildBlocksFromSubMenu(UToolMenu* ParentMenu, FToolMenuEntry& InBlock, TArray<UE::ToolMenus::FSubBlockReference>& SubMenuBlocks)
|
|
{
|
|
const bool bBuiltViaDelegate =
|
|
ConvertWidgetChoice(InBlock.ToolBarData.ComboButtonContextMenuGenerator, ParentMenu->Context).IsBound();
|
|
|
|
const bool bHasChildren = (InBlock.Type == EMultiBlockType::ToolBarComboButton
|
|
|| (InBlock.Type == EMultiBlockType::MenuEntry && InBlock.IsSubMenu()))
|
|
&& !bBuiltViaDelegate;
|
|
|
|
if (!bHasChildren)
|
|
{
|
|
return;
|
|
}
|
|
|
|
UToolMenu* const SubMenu = GenerateSubMenu(ParentMenu, InBlock.Name);
|
|
if (!SubMenu)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Add the blocks reversed to follow a depth-first iteration.
|
|
for (int i = SubMenu->Sections.Num() - 1; i >= 0; --i)
|
|
{
|
|
FToolMenuSection& Section = SubMenu->Sections[i];
|
|
|
|
for (int j = Section.Blocks.Num() - 1; j >= 0; --j)
|
|
{
|
|
FToolMenuEntry& Block = Section.Blocks[j];
|
|
SubMenuBlocks.Push(UE::ToolMenus::FSubBlockReference(SubMenu, &Section, &Block));
|
|
}
|
|
}
|
|
}
|
|
|
|
void UToolMenus::PopulateToolBarBuilderWithTopLevelChildren(FToolBarBuilder& ToolBarBuilder, UToolMenu* ParentMenu, FToolMenuEntry& InBlock, bool bAddSpaceAfterRaisedChildren)
|
|
{
|
|
using namespace UE::ToolMenus;
|
|
|
|
TArray<FSubBlockReference> SubMenuBlocks;
|
|
// Seed the blocks with the passed-in submenu.
|
|
ExtractChildBlocksFromSubMenu(ParentMenu, InBlock, SubMenuBlocks);
|
|
|
|
// Collect the blocks that might be raised to the top level. Also include separators so we can visualize those in
|
|
// the toolbar when they appear between two raised blocks.
|
|
TArray<FSubBlockReference> BlocksToAdd;
|
|
|
|
// Traverse the sub menu blocks we've found thus far to find more grandchild blocks that are raised (raised set to
|
|
// boolean true) or could be dynamically raised (raised set to a TAttribute<bool> driven by a delegate) to the
|
|
// top-level toolbar.
|
|
int32 NumIterations = 0;
|
|
while (SubMenuBlocks.Num() > 0)
|
|
{
|
|
const FSubBlockReference SubMenuBlock = SubMenuBlocks.Pop(EAllowShrinking::No);
|
|
UToolMenu* const SubMenu = SubMenuBlock.ParentMenu;
|
|
FToolMenuEntry* const Block = SubMenuBlock.Entry;
|
|
|
|
// Keep track of how many blocks we've visited to ensure we don't loop indefinitely.
|
|
if (++NumIterations > 5000)
|
|
{
|
|
UE_LOG(LogToolMenus, Warning,
|
|
TEXT("Possible infinite loop for menu with section menu. parent menu: %s, menu: %s, block: %s"),
|
|
*ParentMenu->MenuName.ToString(), *SubMenu->MenuName.ToString(), *Block->Name.ToString());
|
|
break;
|
|
}
|
|
|
|
const TAttribute<bool> ScriptShowInToolbarTopLevel = Block->ScriptObject
|
|
? Block->ScriptObject->CreateShowInToolbarTopLevelAttribute(ParentMenu->Context)
|
|
: TAttribute<bool>();
|
|
|
|
const bool bIsBound = Block->ShowInToolbarTopLevel.IsBound() || ScriptShowInToolbarTopLevel.IsBound();
|
|
const bool bIsSetToValue = !bIsBound && (Block->ShowInToolbarTopLevel.IsSet() || ScriptShowInToolbarTopLevel.IsSet());
|
|
const bool bIsSetToTrueValue = bIsSetToValue && (Block->ShowInToolbarTopLevel.Get() || ScriptShowInToolbarTopLevel.Get());
|
|
if (bIsBound || bIsSetToTrueValue || Block->Type == EMultiBlockType::Separator)
|
|
{
|
|
BlocksToAdd.Add(SubMenuBlock);
|
|
}
|
|
|
|
ExtractChildBlocksFromSubMenu(SubMenu, *Block, SubMenuBlocks);
|
|
}
|
|
|
|
// Do not allow leading separators.
|
|
while (BlocksToAdd.Num() > 0 && BlocksToAdd[0].Entry->Type == EMultiBlockType::Separator)
|
|
{
|
|
BlocksToAdd.RemoveAt(0);
|
|
}
|
|
|
|
// Do not allow trailing separators.
|
|
while (BlocksToAdd.Num() > 0 && BlocksToAdd[BlocksToAdd.Num() - 1].Entry->Type == EMultiBlockType::Separator)
|
|
{
|
|
BlocksToAdd.RemoveAt(BlocksToAdd.Num() - 1);
|
|
}
|
|
|
|
// Do not allow rows of separators.
|
|
for (int i = 1; i < BlocksToAdd.Num(); ++i)
|
|
{
|
|
const bool bIsCurrentSeparator = BlocksToAdd[i].Entry->Type == EMultiBlockType::Separator;
|
|
const bool bWasPreviousSeparator = BlocksToAdd[i - 1].Entry->Type == EMultiBlockType::Separator;
|
|
const bool bPartOfRowOfSeparators = bIsCurrentSeparator && bWasPreviousSeparator;
|
|
|
|
if (bPartOfRowOfSeparators)
|
|
{
|
|
BlocksToAdd.RemoveAt(i--);
|
|
}
|
|
}
|
|
|
|
if (BlocksToAdd.IsEmpty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Dynamic visibility of trailing raised-children spacer.
|
|
TArray<TAttribute<EVisibility>> AllRaisedVisibilities = TArray<TAttribute<EVisibility>>();
|
|
|
|
// Dynamic visibility of separators
|
|
//
|
|
// We add separators in the top-level toolbar between raised entries if the raised entries lived in different
|
|
// sections or if a separator was explicitly added between the raised entries.
|
|
//
|
|
// Since entries can be dynamically raised, added toolbar separators must have dynamic visiblity. To support this,
|
|
// we record the visibility delegates of previously raised entries so separator visiblity delegates can use them.
|
|
//
|
|
// A menu might look like this:
|
|
//
|
|
// |-- previous1 -| |-- previous2 -| |-- previous3 -| |---- next ----|
|
|
// raisedA raisedB SEPARATOR(N-2) raisedC raisedD SEPARATOR(N-1) raisedE raisedF SEPARATOR(N) raisedG raisedH
|
|
//
|
|
// PreviousEntries = (raisedA, raisedB, raisedC, raisedD, raisedE, raisedF)
|
|
// NextEntries = (raisedG, raisedH)
|
|
//
|
|
// Separator visibility is then calculated like this:
|
|
//
|
|
// sep_vis = anyVisible(nextEntries) && anyVisible(PreviousEntries
|
|
//
|
|
|
|
TArray<TAttribute<EVisibility>> PreviousVisibilities;
|
|
// This has to be heap allocated so we can still add do it after a separator visibility delegate captures it.
|
|
TSharedPtr<TArray<TAttribute<EVisibility>>> NextVisibilities = MakeShared<TArray<TAttribute<EVisibility>>>();
|
|
|
|
bool bHasRaisedEntrySinceLastSeparator = false;
|
|
// Seed the previous section with the first block's section so we don't start with adding a separator because
|
|
// sections seem to have changed.
|
|
FName PreviousSectionName = BlocksToAdd[0].Section->Name;
|
|
FName PreviousBlockGroupName = NAME_None;
|
|
for (int i = 0; i < BlocksToAdd.Num(); ++i)
|
|
{
|
|
UToolMenu* const SubMenu = BlocksToAdd[i].ParentMenu;
|
|
FToolMenuSection* const Section = BlocksToAdd[i].Section;
|
|
FToolMenuEntry* const Entry = BlocksToAdd[i].Entry;
|
|
|
|
// Add a separator if one was found or a new section was encountered.
|
|
if (bHasRaisedEntrySinceLastSeparator
|
|
&& (Section->Name != PreviousSectionName || Entry->Type == EMultiBlockType::Separator))
|
|
{
|
|
// Step entry visibility delegate records forward now that we encountered a new separator.
|
|
PreviousVisibilities.Append(*NextVisibilities);
|
|
NextVisibilities = MakeShared<TArray<TAttribute<EVisibility>>>();
|
|
|
|
const TAttribute<EVisibility> VisibilityOverride = TAttribute<EVisibility>::CreateLambda(
|
|
[Previous = PreviousVisibilities, Next = NextVisibilities]()
|
|
{
|
|
// This function calculates this expression and earlies out if possible.
|
|
// const bool bVisible = bAnyNext && bAnyPrevious;
|
|
|
|
bool bAnyNext = false;
|
|
for (const TAttribute<EVisibility>& Visibility : *Next)
|
|
{
|
|
if (Visibility.Get() == EVisibility::Visible)
|
|
{
|
|
bAnyNext = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!bAnyNext)
|
|
{
|
|
return EVisibility::Collapsed;
|
|
}
|
|
|
|
for (const TAttribute<EVisibility>& Visibility : Previous)
|
|
{
|
|
if (Visibility.Get() == EVisibility::Visible)
|
|
{
|
|
return EVisibility::Visible;
|
|
}
|
|
}
|
|
|
|
return EVisibility::Collapsed;
|
|
}
|
|
);
|
|
|
|
const FName UnsetExtensionHook = NAME_None;
|
|
|
|
FMenuEntryResizeParams ResizeParams;
|
|
ResizeParams.VisibleInOverflow = false;
|
|
|
|
ToolBarBuilder.AddSeparator(UnsetExtensionHook, VisibilityOverride, ResizeParams);
|
|
bHasRaisedEntrySinceLastSeparator = false;
|
|
}
|
|
|
|
// Make sure we actually add the entry if the reason we added a separator above was that the section names changed.
|
|
if (Entry->Type != EMultiBlockType::Separator)
|
|
{
|
|
const FName EntryBlockGroupName = Entry->ToolBarData.BlockGroupName;
|
|
if (EntryBlockGroupName != PreviousBlockGroupName)
|
|
{
|
|
if (!PreviousBlockGroupName.IsNone())
|
|
{
|
|
ToolBarBuilder.EndBlockGroup();
|
|
}
|
|
if (!EntryBlockGroupName.IsNone())
|
|
{
|
|
ToolBarBuilder.BeginBlockGroup();
|
|
}
|
|
}
|
|
PreviousBlockGroupName = EntryBlockGroupName;
|
|
|
|
constexpr bool bRaiseToTopLevel = true;
|
|
PopulateToolBarBuilderWithEntry(ToolBarBuilder, SubMenu, *Section, *Entry, bRaiseToTopLevel);
|
|
bHasRaisedEntrySinceLastSeparator = true;
|
|
|
|
constexpr bool bEmbedActionOrCommand = true;
|
|
const TAttribute<EVisibility> EntryVisibility = CalculateToolbarVisibility(SubMenu, *Section, *Entry, bRaiseToTopLevel, bEmbedActionOrCommand);
|
|
|
|
// Keep track of added entries' visibilities so separators can set their visibility override.
|
|
NextVisibilities->Add(EntryVisibility);
|
|
|
|
if (bAddSpaceAfterRaisedChildren)
|
|
{
|
|
AllRaisedVisibilities.Add(EntryVisibility);
|
|
}
|
|
}
|
|
|
|
PreviousSectionName = Section->Name;
|
|
}
|
|
|
|
if (!PreviousBlockGroupName.IsNone())
|
|
{
|
|
ToolBarBuilder.EndBlockGroup();
|
|
}
|
|
|
|
if (bAddSpaceAfterRaisedChildren)
|
|
{
|
|
const FMenuEntryStyleParams StyleParams;
|
|
const FName TutorialHighlightName = NAME_None;
|
|
const bool bSearchable = false;
|
|
const FNewMenuDelegate CustomMenuDelegate;
|
|
|
|
TAttribute<EVisibility> SpacerVisibilityOverride = TAttribute<EVisibility>::CreateLambda(
|
|
[AllRaisedVisibilities]()
|
|
{
|
|
for (const TAttribute<EVisibility>& Visibility : AllRaisedVisibilities)
|
|
{
|
|
if (Visibility.Get() == EVisibility::Visible)
|
|
{
|
|
return EVisibility::Visible;
|
|
}
|
|
}
|
|
|
|
return EVisibility::Collapsed;
|
|
}
|
|
);
|
|
|
|
FMenuEntryResizeParams ResizeParams;
|
|
// Never show spacers in overflow menus.
|
|
ResizeParams.VisibleInOverflow = false;
|
|
// Default clipping priority is 0. Set to a negative number so that the spaces drop before any content.
|
|
ResizeParams.ClippingPriority = -100;
|
|
|
|
const FName StyleName = InBlock.StyleNameOverride.IsNone() ? ParentMenu->StyleName : InBlock.StyleNameOverride;
|
|
const FToolBarStyle ToolbarStyle = ParentMenu->GetStyleSet()->GetWidgetStyle<FToolBarStyle>(StyleName);
|
|
ToolBarBuilder.AddWidget(
|
|
SNew(SSpacer).Size(FVector2D(ToolbarStyle.RaisedChildrenRightPadding, 0)),
|
|
StyleParams,
|
|
TutorialHighlightName,
|
|
bSearchable,
|
|
CustomMenuDelegate,
|
|
SpacerVisibilityOverride,
|
|
ResizeParams
|
|
);
|
|
}
|
|
}
|
|
|
|
void UToolMenus::PopulateToolBarBuilderWithEntry(
|
|
FToolBarBuilder& ToolBarBuilder,
|
|
UToolMenu* MenuData,
|
|
FToolMenuSection& Section,
|
|
FToolMenuEntry& Block,
|
|
bool bIsRaisingToTopLevel,
|
|
bool bIsLastBlockOfLastSection
|
|
)
|
|
{
|
|
if (Block.ToolBarData.ConstructLegacy.IsBound())
|
|
{
|
|
Block.ToolBarData.ConstructLegacy.Execute(ToolBarBuilder, MenuData);
|
|
return;
|
|
}
|
|
|
|
// Override the style name.
|
|
{
|
|
FName OverrideStyleName = Block.ToolBarData.StyleNameOverride;
|
|
if (OverrideStyleName == NAME_None)
|
|
{
|
|
OverrideStyleName = Block.StyleNameOverride;
|
|
}
|
|
|
|
// Add the .Raised suffix for menu entries raised to the top level.
|
|
if (bIsRaisingToTopLevel)
|
|
{
|
|
if (OverrideStyleName != NAME_None)
|
|
{
|
|
OverrideStyleName = ISlateStyle::Join(OverrideStyleName, ".Raised");
|
|
}
|
|
else
|
|
{
|
|
// We have to search up the submenu parent chain here because the immediate menu we're a part of might
|
|
// not have a style set while a parent could.
|
|
const UToolMenu* CurrentMenu = MenuData;
|
|
FName MenuStyleName = NAME_None;
|
|
while (MenuStyleName == NAME_None && CurrentMenu->SubMenuParent)
|
|
{
|
|
CurrentMenu = CurrentMenu->SubMenuParent.Get();
|
|
MenuStyleName = CurrentMenu->StyleName;
|
|
}
|
|
|
|
if (MenuStyleName != NAME_None)
|
|
{
|
|
OverrideStyleName = ISlateStyle::Join(MenuStyleName, ".Raised");
|
|
}
|
|
}
|
|
}
|
|
|
|
ToolBarBuilder.BeginStyleOverride(OverrideStyleName);
|
|
}
|
|
|
|
constexpr bool bEmbedActionOrCommand = false;
|
|
const TAttribute<EVisibility> Visibility = CalculateToolbarVisibility(MenuData, Section, Block, bIsRaisingToTopLevel, bEmbedActionOrCommand);
|
|
|
|
const FUIAction UIAction = [&Block, MenuData]()
|
|
{
|
|
if (Block.ToolBarData.ActionOverride.IsSet())
|
|
{
|
|
return UToolMenus::ConvertUIAction(Block.ToolBarData.ActionOverride.GetValue(), MenuData->Context);
|
|
}
|
|
else
|
|
{
|
|
return UToolMenus::ConvertUIAction(Block, MenuData->Context);
|
|
}
|
|
}();
|
|
|
|
TAttribute<FText> ToolbarLabelOverride;
|
|
if (Block.ToolBarData.LabelOverride.IsSet())
|
|
{
|
|
ToolbarLabelOverride = Block.ToolBarData.LabelOverride;
|
|
}
|
|
else if (const bool bHasIcon = Block.Icon.IsSet() || (Block.Command.IsValid() && Block.Command->GetIcon().IsSet());
|
|
bHasIcon && bIsRaisingToTopLevel)
|
|
{
|
|
// Set the toolbar label to the empty string if we're raising an entry that has an icon. This makes
|
|
// raising/pinning of icons less annoying because the intended design is for them to not have a label. We can
|
|
// still use the ToolbarLabelOverride to bypass this.
|
|
ToolbarLabelOverride = FText();
|
|
}
|
|
|
|
FMenuEntryResizeParams ActualResizeParams = Block.ToolBarData.ResizeParams;
|
|
if (bIsRaisingToTopLevel && !Block.ToolBarData.ResizeParams.VisibleInOverflow.IsSet())
|
|
{
|
|
ActualResizeParams.VisibleInOverflow = false;
|
|
}
|
|
|
|
if (Block.Type == EMultiBlockType::ToolBarButton || (Block.Type == EMultiBlockType::MenuEntry && !Block.IsSubMenu()))
|
|
{
|
|
if (Block.Command.IsValid() && !Block.IsCommandKeybindOnly())
|
|
{
|
|
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, Verbose, TEXT("UI command not found for toolbar entry: %s, toolbar: %s"),
|
|
*Block.Name.ToString(), *MenuData->MenuName.ToString());
|
|
}
|
|
|
|
TSharedPtr<FToolBarButtonBlock> ButtonBlock = ToolBarBuilder.AddToolBarButton(
|
|
Block.Command,
|
|
Block.Name,
|
|
Block.Label,
|
|
Block.ToolTip,
|
|
Block.Icon,
|
|
Block.TutorialHighlightName,
|
|
FNewMenuDelegate(),
|
|
Visibility,
|
|
ToolbarLabelOverride,
|
|
ActualResizeParams
|
|
);
|
|
|
|
if (Block.MakeCustomWidget.IsBound())
|
|
{
|
|
FToolMenuCustomWidgetContext EntryWidgetContext;
|
|
TSharedRef<FMultiBox> MultiBox = ToolBarBuilder.GetMultiBox();
|
|
EntryWidgetContext.StyleSet = MultiBox->GetStyleSet();
|
|
EntryWidgetContext.StyleName = MultiBox->GetStyleName();
|
|
ButtonBlock->SetCustomWidget(Block.MakeCustomWidget.Execute(MenuData->Context, EntryWidgetContext));
|
|
}
|
|
|
|
if (bPopCommandList)
|
|
{
|
|
ToolBarBuilder.PopCommandList();
|
|
}
|
|
}
|
|
else if (Block.ScriptObject)
|
|
{
|
|
UToolMenuEntryScript* ScriptObject = Block.ScriptObject;
|
|
const 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,
|
|
Visibility,
|
|
ToolbarLabelOverride,
|
|
ActualResizeParams
|
|
);
|
|
}
|
|
else
|
|
{
|
|
TSharedPtr<FToolBarButtonBlock> ButtonBlock = ToolBarBuilder.AddToolBarButton(
|
|
UIAction,
|
|
Block.Name,
|
|
Block.Label,
|
|
Block.ToolTip,
|
|
Block.Icon,
|
|
Block.UserInterfaceActionType,
|
|
Block.TutorialHighlightName,
|
|
Visibility,
|
|
ToolbarLabelOverride,
|
|
ActualResizeParams
|
|
);
|
|
|
|
if (Block.MakeCustomWidget.IsBound())
|
|
{
|
|
FToolMenuCustomWidgetContext EntryWidgetContext;
|
|
TSharedRef<FMultiBox> MultiBox = ToolBarBuilder.GetMultiBox();
|
|
EntryWidgetContext.StyleSet = MultiBox->GetStyleSet();
|
|
EntryWidgetContext.StyleName = MultiBox->GetStyleName();
|
|
ButtonBlock->SetCustomWidget(Block.MakeCustomWidget.Execute(MenuData->Context, EntryWidgetContext));
|
|
}
|
|
}
|
|
|
|
if (Block.ToolBarData.OptionsDropdownData.IsValid())
|
|
{
|
|
FOnGetContent OnGetContent = ConvertWidgetChoice(
|
|
Block.ToolBarData.OptionsDropdownData->MenuContentGenerator, MenuData->Context);
|
|
ToolBarBuilder.AddComboButton(
|
|
Block.ToolBarData.OptionsDropdownData->Action,
|
|
OnGetContent,
|
|
Block.Label,
|
|
Block.ToolBarData.OptionsDropdownData->ToolTip,
|
|
Block.Icon,
|
|
true,
|
|
Block.TutorialHighlightName,
|
|
Visibility,
|
|
ToolbarLabelOverride,
|
|
Block.ToolBarData.PlacementOverride,
|
|
Block.UserInterfaceActionType,
|
|
ActualResizeParams
|
|
);
|
|
}
|
|
}
|
|
else if (Block.Type == EMultiBlockType::ToolBarComboButton || (Block.Type == EMultiBlockType::MenuEntry && Block.IsSubMenu()))
|
|
{
|
|
bool bCouldHaveChildren = false;
|
|
FOnGetContent OnGetContent = ConvertWidgetChoice(Block.ToolBarData.ComboButtonContextMenuGenerator, MenuData->Context);
|
|
|
|
// Allow non-tool-menu-creating choices to be applied
|
|
if (!OnGetContent.IsBound() && !Block.SubMenuData.ConstructMenu.NewToolMenu.IsBound())
|
|
{
|
|
OnGetContent = ConvertWidgetChoice(Block.SubMenuData.ConstructMenu, MenuData->Context);
|
|
}
|
|
|
|
if (!OnGetContent.IsBound())
|
|
{
|
|
// Handle tool-menu-generating lambdas.
|
|
// Make sure to keep a strong reference to this submenu so it stays around until it is opened. This is
|
|
// needed here because we're could be a submenu of a submenu, so not even our parent is added to the Menus
|
|
// map and therefore our parent could have been gargage collected before this submenu is opened.
|
|
OnGetContent = FOnGetContent::CreateLambda(
|
|
[this, StrongMenuData = TStrongObjectPtr<UToolMenu>(MenuData), BlockName = Block.Name]() -> TSharedRef<SWidget>
|
|
{
|
|
return this->GenerateToolbarComboButtonMenu(StrongMenuData.Get(), BlockName);
|
|
}
|
|
);
|
|
bCouldHaveChildren = true;
|
|
}
|
|
|
|
ToolBarBuilder.AddComboButton(
|
|
UIAction,
|
|
OnGetContent,
|
|
Block.Label,
|
|
Block.ToolTip,
|
|
Block.Icon,
|
|
Block.ToolBarData.bSimpleComboBox,
|
|
Block.TutorialHighlightName,
|
|
Visibility,
|
|
ToolbarLabelOverride,
|
|
Block.ToolBarData.PlacementOverride,
|
|
Block.UserInterfaceActionType,
|
|
ActualResizeParams
|
|
);
|
|
|
|
// Also add any top-level flagged children to the toolbar.
|
|
if (bCouldHaveChildren && !bIsRaisingToTopLevel)
|
|
{
|
|
PopulateToolBarBuilderWithTopLevelChildren(ToolBarBuilder, MenuData, Block, !bIsLastBlockOfLastSection);
|
|
}
|
|
}
|
|
else if (Block.Type == EMultiBlockType::Separator)
|
|
{
|
|
ToolBarBuilder.AddSeparator(Block.Name);
|
|
}
|
|
else if (Block.Type == EMultiBlockType::Widget)
|
|
{
|
|
TSharedPtr<SWidget> Widget;
|
|
|
|
if (Block.MakeCustomWidget.IsBound())
|
|
{
|
|
FToolMenuCustomWidgetContext EntryWidgetContext;
|
|
TSharedRef<FMultiBox> MultiBox = ToolBarBuilder.GetMultiBox();
|
|
EntryWidgetContext.StyleSet = MultiBox->GetStyleSet();
|
|
EntryWidgetContext.StyleName = MultiBox->GetStyleName();
|
|
Widget = Block.MakeCustomWidget.Execute(MenuData->Context, EntryWidgetContext);
|
|
}
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
else if (Block.MakeWidget.IsBound())
|
|
{
|
|
Widget = Block.MakeWidget.Execute(MenuData->Context);
|
|
}
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
|
|
FMenuEntryStyleParams StyleParams = Block.WidgetData.StyleParams;
|
|
StyleParams.HorizontalAlignment = HAlign_Fill;
|
|
// Default to vertical fill if vertical alignment hasn't been modified for this particular entry.
|
|
if (!StyleParams.VerticalAlignment.IsSet())
|
|
{
|
|
StyleParams.VerticalAlignment = VAlign_Fill;
|
|
}
|
|
|
|
ToolBarBuilder.AddWidget(Widget.ToSharedRef(), StyleParams, Block.TutorialHighlightName, Block.WidgetData.bSearchable, FNewMenuDelegate(), Visibility, ActualResizeParams);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogToolMenus, Warning, TEXT("Toolbar '%s', item '%s', Toolbars do not support: %s"),
|
|
*MenuData->MenuName.ToString(), *Block.Name.ToString(), *UEnum::GetValueAsString(Block.Type));
|
|
}
|
|
|
|
ToolBarBuilder.EndStyleOverride();
|
|
}
|
|
|
|
void UToolMenus::PopulateToolBarBuilder(FToolBarBuilder& ToolBarBuilder, UToolMenu* MenuData)
|
|
{
|
|
if (GetEditMenusMode() && !MenuData->IsEditing() && EditMenuDelegate.IsBound())
|
|
{
|
|
TWeakObjectPtr<UToolMenu> WeakMenuPtr = MenuData;
|
|
const FName MenuName = MenuData->GetMenuName();
|
|
ToolBarBuilder.BeginSection(MenuName);
|
|
ToolBarBuilder.AddToolBarButton(
|
|
FExecuteAction::CreateLambda([MenuName, WeakMenuPtr]()
|
|
{
|
|
FPlatformApplicationMisc::ClipboardCopy(*MenuName.ToString());
|
|
if (UToolMenu* InMenu = WeakMenuPtr.Get())
|
|
{
|
|
UToolMenus::Get()->EditMenuDelegate.ExecuteIfBound(InMenu);
|
|
}
|
|
}),
|
|
"MenuName",
|
|
LOCTEXT("EditMenu", "Edit Menu"),
|
|
LOCTEXT("EditMenu_ToolTip", "Open menu editor"),
|
|
EditToolbarIcon
|
|
);
|
|
ToolBarBuilder.EndSection();
|
|
}
|
|
|
|
// Add the sections grouped by alignment with SSpacers in between. This visually separates them and allows users
|
|
// to align sections to appear first, middle, or last. Default-aligned sections appear grouped with first-aligned
|
|
// sections but appear after them.
|
|
TArray<FToolMenuSection*> SortedSections;
|
|
SortedSections.Reserve(MenuData->Sections.Num());
|
|
for (int32 Index = 0; Index < MenuData->Sections.Num(); ++Index)
|
|
{
|
|
SortedSections.Add(&MenuData->Sections[Index]);
|
|
}
|
|
|
|
SortedSections.StableSort([](const FToolMenuSection& A, const FToolMenuSection& B)
|
|
{
|
|
return UE::ToolMenus::Private::SortSectionAlignment(A.Alignment, B.Alignment) < 0;
|
|
});
|
|
|
|
EToolMenuSectionAlign LastAlignment = EToolMenuSectionAlign::First;
|
|
for (int32 SectionIndex = 0; SectionIndex < SortedSections.Num(); ++SectionIndex)
|
|
{
|
|
FToolMenuSection& Section = *SortedSections[SectionIndex];
|
|
|
|
if (Section.Alignment != LastAlignment)
|
|
{
|
|
const bool bIsMiddleOrLast = Section.Alignment == EToolMenuSectionAlign::Middle || Section.Alignment == EToolMenuSectionAlign::Last;
|
|
|
|
// Add a spacer before the middle and last alignment groups, and only if we've already added a section
|
|
if (bIsMiddleOrLast && SectionIndex > 0)
|
|
{
|
|
static constexpr float AlmostZero = UE_KINDA_SMALL_NUMBER; // Using 0.0f results in different behavior, so we just use a small, sub-1 pixel value (which is also used as a proportion of the overall layout space).
|
|
|
|
FMenuEntryStyleParams StyleParams;
|
|
|
|
StyleParams.HorizontalAlignment = HAlign_Right;
|
|
StyleParams.SizeRule = FSizeParam::ESizeRule::SizeRule_Stretch;
|
|
StyleParams.FillSize = AlmostZero;
|
|
StyleParams.FillSizeMin = AlmostZero; // Allow the spacer to shrink to nothing if another widget needs the space.
|
|
StyleParams.MinimumSize = AlmostZero;
|
|
StyleParams.DesiredWidthOverride = StyleParams.DesiredHeightOverride = AlmostZero;
|
|
|
|
FMenuEntryResizeParams ResizeParams;
|
|
// Never show spacers in overflow menus.
|
|
ResizeParams.VisibleInOverflow = false;
|
|
// Prevent our spacers from overflowing. This will keep them in the toolbar, but they will still shrink so
|
|
// overflow of other entries will not be affected. What will be affected, however, is stretching of the
|
|
// toolbar after entries have been clipped. By keeping the stretchers in the toolbar, we can grow them to
|
|
// fill up any space left over when a widget is clipped.
|
|
ResizeParams.AllowClipping = false;
|
|
|
|
ToolBarBuilder.AddWidget(
|
|
SNew(SSpacer), StyleParams, NAME_None, false, FNewMenuDelegate(), TAttribute<EVisibility>(), ResizeParams
|
|
);
|
|
}
|
|
}
|
|
|
|
if (Section.Construct.NewToolBarDelegateLegacy.IsBound())
|
|
{
|
|
Section.Construct.NewToolBarDelegateLegacy.Execute(ToolBarBuilder, MenuData);
|
|
continue;
|
|
}
|
|
|
|
const bool bIsLastSection = SectionIndex >= SortedSections.Num() - 1;
|
|
// "Default" sections are placed directly after "first" sections, but on the same side.
|
|
// Therefor, they should not be considered "first".
|
|
const bool bFirstSectionInAlignmentGroup = Section.Alignment != LastAlignment && Section.Alignment != EToolMenuSectionAlign::Default;
|
|
|
|
const bool bSectionShouldHaveSeparator = MenuData->bSeparateSections && !bFirstSectionInAlignmentGroup;
|
|
ToolBarBuilder.BeginSection(Section.Name, bSectionShouldHaveSeparator, Section.ResizeParams);
|
|
|
|
FName PreviousBlockGroupName = NAME_None;
|
|
for (int BlockIndex = 0; BlockIndex < Section.Blocks.Num(); ++BlockIndex)
|
|
{
|
|
const bool bIsLastBlock = BlockIndex >= Section.Blocks.Num() - 1;
|
|
FToolMenuEntry& Block = Section.Blocks[BlockIndex];
|
|
|
|
// This is the top level of the toolbar; nothing is being elevated.
|
|
const bool bRaiseToTopLevel = false;
|
|
const bool bIsLastBlockOfLastSection = bIsLastBlock && bIsLastSection;
|
|
|
|
const FName EntryBlockGroupName = Block.ToolBarData.BlockGroupName;
|
|
if (EntryBlockGroupName != PreviousBlockGroupName)
|
|
{
|
|
if (!PreviousBlockGroupName.IsNone())
|
|
{
|
|
ToolBarBuilder.EndBlockGroup();
|
|
}
|
|
if (!EntryBlockGroupName.IsNone())
|
|
{
|
|
ToolBarBuilder.BeginBlockGroup();
|
|
}
|
|
}
|
|
PreviousBlockGroupName = EntryBlockGroupName;
|
|
|
|
PopulateToolBarBuilderWithEntry(ToolBarBuilder, MenuData, Section, Block, bRaiseToTopLevel, bIsLastBlockOfLastSection);
|
|
}
|
|
|
|
if (!PreviousBlockGroupName.IsNone())
|
|
{
|
|
ToolBarBuilder.EndBlockGroup();
|
|
}
|
|
|
|
ToolBarBuilder.EndSection();
|
|
|
|
LastAlignment = Section.Alignment;
|
|
}
|
|
|
|
AddReferencedContextObjects(ToolBarBuilder.GetMultiBox(), MenuData);
|
|
}
|
|
|
|
void UToolMenus::PopulateMenuBarBuilder(FMenuBarBuilder& MenuBarBuilder, UToolMenu* MenuData)
|
|
{
|
|
for (int i=0; i < MenuData->Sections.Num(); ++i)
|
|
{
|
|
const FToolMenuSection& Section = MenuData->Sections[i];
|
|
for (const FToolMenuEntry& Block : Section.Blocks)
|
|
{
|
|
if (Block.SubMenuData.ConstructMenu.OnGetContent.IsBound())
|
|
{
|
|
MenuBarBuilder.AddPullDownMenu(
|
|
Block.Label,
|
|
Block.ToolTip,
|
|
Block.SubMenuData.ConstructMenu.OnGetContent,
|
|
Block.Name
|
|
);
|
|
}
|
|
else if (Block.SubMenuData.ConstructMenu.NewMenuLegacy.IsBound())
|
|
{
|
|
MenuBarBuilder.AddPullDownMenu(
|
|
Block.Label,
|
|
Block.ToolTip,
|
|
Block.SubMenuData.ConstructMenu.NewMenuLegacy,
|
|
Block.Name
|
|
);
|
|
}
|
|
else
|
|
{
|
|
MenuBarBuilder.AddPullDownMenu(
|
|
Block.Label,
|
|
Block.ToolTip,
|
|
FNewMenuDelegate::CreateUObject(this, &UToolMenus::PopulateSubMenu, TWeakObjectPtr<UToolMenu>(MenuData), Block, Block.Name),
|
|
Block.Name
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
const bool bIsEditing = MenuData->IsEditing();
|
|
if (GetEditMenusMode() && !bIsEditing && EditMenuDelegate.IsBound())
|
|
{
|
|
TWeakObjectPtr<UToolMenu> WeakMenuPtr = MenuData;
|
|
const FName MenuName = MenuData->GetMenuName();
|
|
MenuBarBuilder.AddMenuEntry(
|
|
LOCTEXT("EditMenuBar_Label", "Edit Menu"),
|
|
FText::Format(LOCTEXT("EditMenuBar_ToolTip", "Edit Menu: {0}"), FText::FromName(MenuName)),
|
|
EditMenuIcon,
|
|
FExecuteAction::CreateLambda([MenuName, WeakMenuPtr]()
|
|
{
|
|
FPlatformApplicationMisc::ClipboardCopy(*MenuName.ToString());
|
|
if (UToolMenu* InMenu = WeakMenuPtr.Get())
|
|
{
|
|
UToolMenus::Get()->EditMenuDelegate.ExecuteIfBound(InMenu);
|
|
}
|
|
}),
|
|
"MenuName"
|
|
);
|
|
}
|
|
|
|
AddReferencedContextObjects(MenuBarBuilder.GetMultiBox(), MenuData);
|
|
}
|
|
|
|
FOnGetContent UToolMenus::ConvertWidgetChoice(const FNewToolMenuChoice& 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 = UToolMenus::Get()->NewToolMenuObject(FName(TEXT("NewToolMenu")), NAME_None);
|
|
MenuData->Context = Context;
|
|
ToCall.Execute(MenuData);
|
|
return UToolMenus::Get()->GenerateWidget(MenuData);
|
|
}
|
|
|
|
return SNullWidget::NullWidget;
|
|
});
|
|
}
|
|
else if (Choice.NewMenuLegacy.IsBound())
|
|
{
|
|
return FOnGetContent::CreateLambda([ToCall = Choice.NewMenuLegacy, Context]()
|
|
{
|
|
if (ToCall.IsBound())
|
|
{
|
|
FMenuBuilder MenuBuilder(true, Context.CommandList, Context.GetAllExtenders());
|
|
ToCall.Execute(MenuBuilder);
|
|
return MenuBuilder.MakeWidget();
|
|
}
|
|
|
|
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(Block.Name, 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;
|
|
}
|
|
|
|
bool UToolMenus::CanSafelyRouteCall()
|
|
{
|
|
return !(GIntraFrameDebuggingGameThread || FUObjectThreadContext::Get().IsRoutingPostLoad);
|
|
}
|
|
|
|
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]()
|
|
{
|
|
if (DelegateToCall.IsBound() && UToolMenus::CanSafelyRouteCall())
|
|
{
|
|
return DelegateToCall.Execute(Context);
|
|
}
|
|
|
|
return false;
|
|
});
|
|
}
|
|
|
|
if (Actions.GetActionCheckState.IsBound())
|
|
{
|
|
UIAction.GetActionCheckState.BindLambda([DelegateToCall = Actions.GetActionCheckState, Context]()
|
|
{
|
|
if (DelegateToCall.IsBound() && UToolMenus::CanSafelyRouteCall())
|
|
{
|
|
return DelegateToCall.Execute(Context);
|
|
}
|
|
|
|
return ECheckBoxState::Unchecked;
|
|
});
|
|
}
|
|
|
|
if (Actions.IsActionVisibleDelegate.IsBound())
|
|
{
|
|
UIAction.IsActionVisibleDelegate.BindLambda([DelegateToCall = Actions.IsActionVisibleDelegate, Context]()
|
|
{
|
|
if (DelegateToCall.IsBound() && UToolMenus::CanSafelyRouteCall())
|
|
{
|
|
return DelegateToCall.Execute(Context);
|
|
}
|
|
|
|
return true;
|
|
});
|
|
}
|
|
|
|
return UIAction;
|
|
}
|
|
|
|
FUIAction UToolMenus::ConvertScriptObjectToUIAction(UToolMenuEntryScript* ScriptObject, const FToolMenuContext& Context)
|
|
{
|
|
FUIAction UIAction;
|
|
|
|
if (ScriptObject)
|
|
{
|
|
TWeakObjectPtr<UToolMenuEntryScript> WeakScriptObject(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.BindLambda([WeakScriptObject, Context]()
|
|
{
|
|
UToolMenuEntryScript* Object = UToolMenuEntryScript::GetIfCanSafelyRouteCall(WeakScriptObject);
|
|
return Object ? Object->CanExecute(Context) : false;
|
|
});
|
|
}
|
|
|
|
static const FName GetCheckStateName = GET_FUNCTION_NAME_CHECKED(UToolMenuEntryScript, GetCheckState);
|
|
if (ScriptClass->IsFunctionImplementedInScript(GetCheckStateName))
|
|
{
|
|
UIAction.GetActionCheckState.BindLambda([WeakScriptObject, Context]()
|
|
{
|
|
UToolMenuEntryScript* Object = UToolMenuEntryScript::GetIfCanSafelyRouteCall(WeakScriptObject);
|
|
return Object ? Object->GetCheckState(Context) : ECheckBoxState::Unchecked;
|
|
});
|
|
}
|
|
|
|
static const FName IsVisibleName = GET_FUNCTION_NAME_CHECKED(UToolMenuEntryScript, IsVisible);
|
|
if (ScriptClass->IsFunctionImplementedInScript(IsVisibleName))
|
|
{
|
|
UIAction.IsActionVisibleDelegate.BindLambda([WeakScriptObject, Context]()
|
|
{
|
|
UToolMenuEntryScript* Object = UToolMenuEntryScript::GetIfCanSafelyRouteCall(WeakScriptObject);
|
|
return Object ? Object->IsVisible(Context) : true;
|
|
});
|
|
}
|
|
}
|
|
|
|
return UIAction;
|
|
}
|
|
|
|
void UToolMenus::ExecuteStringCommand(const FToolMenuStringCommand StringCommand, FName MenuName, 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());
|
|
}
|
|
|
|
ToolMenus->OnStringCommandExecuted.Broadcast(MenuName, TypeName);
|
|
}
|
|
}
|
|
|
|
TAttribute<EVisibility> UToolMenus::CalculateToolbarVisibility(UToolMenu* Menu, FToolMenuSection& Section, FToolMenuEntry& Entry, bool bIsRaisingToTopLevel, bool bEmbedActionOrCommand)
|
|
{
|
|
// Single values get returned directly. Allow that to cause zero allocations.
|
|
TArray<FToolMenuVisibilityChoice, TInlineAllocator<1>> Visibilities;
|
|
|
|
if (Section.Visibility.IsSet())
|
|
{
|
|
Visibilities.Add(Section.Visibility);
|
|
}
|
|
|
|
if (bIsRaisingToTopLevel)
|
|
{
|
|
FToolMenuVisibilityChoice ShowInTopLevel = Entry.ShowInToolbarTopLevel;
|
|
if (Entry.ScriptObject)
|
|
{
|
|
// Allow scripts to override ShowInToolbarTopLevel.
|
|
if (const TAttribute<bool> ScriptShowInToolbarTopLevel = Entry.ScriptObject->CreateShowInToolbarTopLevelAttribute(Menu->Context);
|
|
ScriptShowInToolbarTopLevel.IsSet())
|
|
{
|
|
ShowInTopLevel = UE::ToolMenus::Private::CombineVisibility(
|
|
ScriptShowInToolbarTopLevel,
|
|
ShowInTopLevel
|
|
);
|
|
}
|
|
}
|
|
|
|
if (ShowInTopLevel.IsSet())
|
|
{
|
|
Visibilities.Add(ShowInTopLevel);
|
|
}
|
|
}
|
|
|
|
if (Entry.Visibility.IsSet())
|
|
{
|
|
Visibilities.Add(Entry.Visibility);
|
|
}
|
|
|
|
if (bEmbedActionOrCommand)
|
|
{
|
|
if (Entry.Command.IsValid() && !Entry.IsCommandKeybindOnly())
|
|
{
|
|
TSharedPtr<const FUICommandList> CommandListForAction;
|
|
if (const FUIAction* FoundAction = Entry.GetActionForCommand(Menu->Context, CommandListForAction))
|
|
{
|
|
if (FoundAction->IsActionVisibleDelegate.IsBound())
|
|
{
|
|
Visibilities.Add(FoundAction->IsActionVisibleDelegate);
|
|
}
|
|
}
|
|
}
|
|
|
|
const FUIAction UIAction = [&Entry, Menu]()
|
|
{
|
|
if (Entry.ToolBarData.ActionOverride.IsSet())
|
|
{
|
|
return UToolMenus::ConvertUIAction(Entry.ToolBarData.ActionOverride.GetValue(), Menu->Context);
|
|
}
|
|
else
|
|
{
|
|
return UToolMenus::ConvertUIAction(Entry, Menu->Context);
|
|
}
|
|
}();
|
|
|
|
if (UIAction.IsActionVisibleDelegate.IsBound())
|
|
{
|
|
Visibilities.Add(UIAction.IsActionVisibleDelegate);
|
|
}
|
|
}
|
|
|
|
if (Visibilities.Num() == 0)
|
|
{
|
|
return TAttribute<EVisibility>();
|
|
}
|
|
|
|
if (Visibilities.Num() == 1)
|
|
{
|
|
return Visibilities[0];
|
|
}
|
|
|
|
return TAttribute<EVisibility>::CreateLambda([Visibilities]
|
|
{
|
|
for (const FToolMenuVisibilityChoice& VisibilityChoice : Visibilities)
|
|
{
|
|
const EVisibility Visibility = VisibilityChoice.Get();
|
|
if (Visibility != EVisibility::Visible)
|
|
{
|
|
return Visibility;
|
|
}
|
|
}
|
|
|
|
return EVisibility::Visible;
|
|
});
|
|
}
|
|
|
|
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 UToolMenu* InMenu)
|
|
{
|
|
if (InMenu)
|
|
{
|
|
auto& References = WidgetObjectReferences.FindOrAdd(InMultiBox);
|
|
References.AddUnique(InMenu);
|
|
for (const TWeakObjectPtr<UObject> WeakObject : InMenu->Context.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();
|
|
}
|
|
}
|
|
|
|
for (auto WidgetsForMenuNameIt = This->GeneratedMenuWidgets.CreateIterator(); WidgetsForMenuNameIt; ++WidgetsForMenuNameIt)
|
|
{
|
|
TSharedPtr<FGeneratedToolMenuWidgets>& WidgetsForMenuName = WidgetsForMenuNameIt->Value;
|
|
|
|
for (auto Instance = WidgetsForMenuName->Instances.CreateIterator(); Instance; ++Instance)
|
|
{
|
|
if (Instance->Get()->Widget.IsValid())
|
|
{
|
|
Collector.AddReferencedObject(Instance->Get()->GeneratedMenu, InThis);
|
|
}
|
|
else
|
|
{
|
|
Instance.RemoveCurrent();
|
|
}
|
|
}
|
|
|
|
if (WidgetsForMenuName->Instances.Num() == 0)
|
|
{
|
|
WidgetsForMenuNameIt.RemoveCurrent();
|
|
}
|
|
}
|
|
|
|
Super::AddReferencedObjects(InThis, Collector);
|
|
}
|
|
|
|
UToolMenu* UToolMenus::GenerateMenuOrSubMenuForEdit(const UToolMenu* InMenu)
|
|
{
|
|
// Make copy of context so we can set bIsEditing flag on it
|
|
FToolMenuContext NewMenuContext = InMenu->Context;
|
|
NewMenuContext.bIsEditing = true;
|
|
|
|
if (!InMenu->SubMenuParent)
|
|
{
|
|
return GenerateMenu(InMenu->GetMenuName(), NewMenuContext);
|
|
}
|
|
|
|
// Generate each menu leading up to the final submenu because sub-menus are not required to be registered
|
|
TArray<const UToolMenu*> SubMenuChain = InMenu->GetSubMenuChain();
|
|
if (SubMenuChain.Num() > 0)
|
|
{
|
|
UToolMenu* CurrentGeneratedMenu = GenerateMenu(SubMenuChain[0]->GetMenuName(), NewMenuContext);
|
|
for (int32 i=1; i < SubMenuChain.Num(); ++i)
|
|
{
|
|
if (UToolMenu* Menu = GenerateSubMenu(CurrentGeneratedMenu, SubMenuChain[i]->SubMenuSourceEntryName))
|
|
{
|
|
CurrentGeneratedMenu = Menu;
|
|
}
|
|
else
|
|
{
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
return CurrentGeneratedMenu;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void UToolMenus::AddMenuSubstitutionDuringGenerate(const FName OriginalMenu, const FName NewMenu)
|
|
{
|
|
MenuSubstitutionsDuringGenerate.Add(OriginalMenu, NewMenu);
|
|
}
|
|
|
|
void UToolMenus::RemoveSubstitutionDuringGenerate(const FName InMenu)
|
|
{
|
|
if (const FName* FoundOverrideMenuName = MenuSubstitutionsDuringGenerate.Find(InMenu))
|
|
{
|
|
const FName OverrideMenuName = *FoundOverrideMenuName;
|
|
|
|
// Update all active widget instances of this menu
|
|
if (TSharedPtr<FGeneratedToolMenuWidgets> OverrideMenuWidgets = GeneratedMenuWidgets.FindRef(OverrideMenuName))
|
|
{
|
|
if (TSharedPtr<FGeneratedToolMenuWidgets> DestMenuWidgets = GeneratedMenuWidgets.FindRef(InMenu))
|
|
{
|
|
DestMenuWidgets->Instances.Append(OverrideMenuWidgets->Instances);
|
|
}
|
|
else
|
|
{
|
|
GeneratedMenuWidgets.Add(InMenu, OverrideMenuWidgets);
|
|
}
|
|
|
|
GeneratedMenuWidgets.Remove(OverrideMenuName);
|
|
}
|
|
|
|
MenuSubstitutionsDuringGenerate.Remove(InMenu);
|
|
|
|
CleanupStaleWidgetsNextTick();
|
|
}
|
|
}
|
|
|
|
UToolMenu* UToolMenus::GenerateMenu(const FName Name, const FToolMenuContext& InMenuContext)
|
|
{
|
|
return GenerateMenuFromHierarchy(CollectHierarchy(Name), InMenuContext);
|
|
}
|
|
|
|
UToolMenu* UToolMenus::GenerateMenuFromHierarchy(const TArray<UToolMenu*>& Hierarchy, const FToolMenuContext& InMenuContext)
|
|
{
|
|
UToolMenu* GeneratedMenu = NewToolMenuObject(FName(TEXT("GeneratedMenuFromHierarchy")), NAME_None);
|
|
|
|
if (Hierarchy.Num() > 0)
|
|
{
|
|
GeneratedMenu->InitGeneratedCopy(Hierarchy[0], Hierarchy.Last()->MenuName, &InMenuContext);
|
|
for (UToolMenu* HiearchyItem : Hierarchy)
|
|
{
|
|
if (HiearchyItem && !HiearchyItem->bExtendersEnabled)
|
|
{
|
|
GeneratedMenu->SetExtendersEnabled(false);
|
|
break;
|
|
}
|
|
}
|
|
AssembleMenuHierarchy(GeneratedMenu, Hierarchy);
|
|
}
|
|
|
|
return GeneratedMenu;
|
|
}
|
|
|
|
TSharedRef<SWidget> UToolMenus::GenerateWidget(const FName InName, const FToolMenuContext& InMenuContext)
|
|
{
|
|
OnPreGenerateWidget.Broadcast(InName, InMenuContext);
|
|
|
|
UToolMenu* Generated = GenerateMenu(InName, InMenuContext);
|
|
TSharedRef<SWidget> Result = GenerateWidget(Generated);
|
|
|
|
OnPostGenerateWidget.Broadcast(InName, Generated);
|
|
|
|
return Result;
|
|
}
|
|
|
|
TSharedRef<SWidget> UToolMenus::GenerateWidget(const TArray<UToolMenu*>& Hierarchy, const FToolMenuContext& InMenuContext)
|
|
{
|
|
if (Hierarchy.Num() == 0)
|
|
{
|
|
return SNullWidget::NullWidget;
|
|
}
|
|
|
|
UToolMenu* Generated = GenerateMenuFromHierarchy(Hierarchy, InMenuContext);
|
|
return GenerateWidget(Generated);
|
|
}
|
|
|
|
TSharedRef<SWidget> UToolMenus::GenerateWidget(UToolMenu* GeneratedMenu)
|
|
{
|
|
CleanupStaleWidgetsNextTick();
|
|
|
|
const ISlateStyle* StyleSetNotNull = GeneratedMenu->GetStyleSet();
|
|
const bool bHadStyleSet = StyleSetNotNull != nullptr;
|
|
if (!bHadStyleSet)
|
|
{
|
|
// Avoid crash when style sets are unregistered/deleted, GetStyleSet() will report an ensure when that happens but return null and menu builders crash when passed null StyleSet.
|
|
StyleSetNotNull = &FCoreStyle::Get();
|
|
}
|
|
|
|
TSharedPtr<SWidget> GeneratedWidget;
|
|
if (GeneratedMenu->IsEditing())
|
|
{
|
|
// Convert toolbar into menu during editing
|
|
if (GeneratedMenu->MenuType == EMultiBoxType::ToolBar || GeneratedMenu->MenuType == EMultiBoxType::VerticalToolBar || GeneratedMenu->MenuType == EMultiBoxType::UniformToolBar || GeneratedMenu->MenuType == EMultiBoxType::SlimHorizontalToolBar || GeneratedMenu->MenuType == EMultiBoxType::SlimWrappingToolBar)
|
|
{
|
|
for (FToolMenuSection& Section : GeneratedMenu->Sections)
|
|
{
|
|
for (FToolMenuEntry& Entry : Section.Blocks)
|
|
{
|
|
ModifyEntryForEditDialog(Entry);
|
|
}
|
|
}
|
|
}
|
|
|
|
FMenuBuilder MenuBuilder(GeneratedMenu->bShouldCloseWindowAfterMenuSelection, GeneratedMenu->Context.CommandList, GeneratedMenu->Context.GetAllExtenders(), GeneratedMenu->bCloseSelfOnly, StyleSetNotNull, GeneratedMenu->bSearchable, GeneratedMenu->MenuName);
|
|
|
|
// Default consistent style is applied, necessary for toolbars to be displayed as menus
|
|
//if (bHadStyleSet && GeneratedMenu->StyleName != NAME_None)
|
|
//{
|
|
// MenuBuilder.SetStyle(StyleSetNotNull, GeneratedMenu->StyleName);
|
|
//}
|
|
|
|
MenuBuilder.SetExtendersEnabled(GeneratedMenu->bExtendersEnabled);
|
|
PopulateMenuBuilder(MenuBuilder, GeneratedMenu);
|
|
if (GeneratedMenu->ModifyBlockWidgetAfterMake.IsBound())
|
|
{
|
|
MenuBuilder.GetMultiBox()->ModifyBlockWidgetAfterMake = GeneratedMenu->ModifyBlockWidgetAfterMake;
|
|
}
|
|
TSharedRef<SWidget> Result = MenuBuilder.MakeWidget();
|
|
GeneratedWidget = Result;
|
|
}
|
|
else if (GeneratedMenu->MenuType == EMultiBoxType::Menu)
|
|
{
|
|
FMenuBuilder MenuBuilder(GeneratedMenu->bShouldCloseWindowAfterMenuSelection, GeneratedMenu->Context.CommandList, GeneratedMenu->Context.GetAllExtenders(), GeneratedMenu->bCloseSelfOnly, StyleSetNotNull, GeneratedMenu->bSearchable, GeneratedMenu->MenuName);
|
|
|
|
if (bHadStyleSet && GeneratedMenu->StyleName != NAME_None)
|
|
{
|
|
MenuBuilder.SetStyle(StyleSetNotNull, GeneratedMenu->StyleName);
|
|
}
|
|
|
|
MenuBuilder.SetExtendersEnabled(GeneratedMenu->bExtendersEnabled);
|
|
PopulateMenuBuilder(MenuBuilder, GeneratedMenu);
|
|
TSharedRef<SWidget> Result = MenuBuilder.MakeWidget(nullptr, GeneratedMenu->MaxHeight);
|
|
GeneratedWidget = Result;
|
|
}
|
|
else if (GeneratedMenu->MenuType == EMultiBoxType::MenuBar)
|
|
{
|
|
FMenuBarBuilder MenuBarBuilder(GeneratedMenu->Context.CommandList, GeneratedMenu->Context.GetAllExtenders(), StyleSetNotNull, GeneratedMenu->MenuName);
|
|
|
|
if (bHadStyleSet && GeneratedMenu->StyleName != NAME_None)
|
|
{
|
|
MenuBarBuilder.SetStyle(StyleSetNotNull, GeneratedMenu->StyleName);
|
|
}
|
|
|
|
MenuBarBuilder.SetExtendersEnabled(GeneratedMenu->bExtendersEnabled);
|
|
PopulateMenuBarBuilder(MenuBarBuilder, GeneratedMenu);
|
|
TSharedRef<SWidget> Result = MenuBarBuilder.MakeWidget();
|
|
GeneratedWidget = Result;
|
|
}
|
|
else if (GeneratedMenu->MenuType == EMultiBoxType::ToolBar || GeneratedMenu->MenuType == EMultiBoxType::VerticalToolBar || GeneratedMenu->MenuType == EMultiBoxType::UniformToolBar || GeneratedMenu->MenuType == EMultiBoxType::SlimHorizontalToolBar || GeneratedMenu->MenuType == EMultiBoxType::SlimWrappingToolBar)
|
|
{
|
|
FToolBarBuilder ToolbarBuilder(GeneratedMenu->MenuType, GeneratedMenu->Context.CommandList, GeneratedMenu->MenuName, GeneratedMenu->Context.GetAllExtenders(), GeneratedMenu->bToolBarForceSmallIcons);
|
|
ToolbarBuilder.SetExtendersEnabled(GeneratedMenu->bExtendersEnabled);
|
|
ToolbarBuilder.SetIsFocusable(GeneratedMenu->bToolBarIsFocusable);
|
|
ToolbarBuilder.SetAllowWrapButton(GeneratedMenu->bAllowToolBarWrapButton);
|
|
|
|
if (bHadStyleSet && GeneratedMenu->StyleName != NAME_None)
|
|
{
|
|
ToolbarBuilder.SetStyle(StyleSetNotNull, GeneratedMenu->StyleName);
|
|
}
|
|
|
|
PopulateToolBarBuilder(ToolbarBuilder, GeneratedMenu);
|
|
TSharedRef<SWidget> Result = ToolbarBuilder.MakeWidget();
|
|
GeneratedWidget = Result;
|
|
}
|
|
|
|
TSharedPtr<FGeneratedToolMenuWidgets>& WidgetsForMenuName = GeneratedMenuWidgets.FindOrAdd(GeneratedMenu->MenuName);
|
|
if (!WidgetsForMenuName.IsValid())
|
|
{
|
|
WidgetsForMenuName = MakeShared<FGeneratedToolMenuWidgets>();
|
|
}
|
|
|
|
// Store a copy so that we can call 'Refresh' on menus not in the database
|
|
FGeneratedToolMenuWidget& GeneratedMenuWidget = *WidgetsForMenuName->Instances.Add_GetRef(MakeShared<FGeneratedToolMenuWidget>());
|
|
GeneratedMenuWidget.OriginalMenu = GeneratedMenu;
|
|
GeneratedMenuWidget.GeneratedMenu = DuplicateObject<UToolMenu>(GeneratedMenu, this, MakeUniqueObjectName(this, UToolMenus::StaticClass(), FName("MenuForRefresh")));
|
|
GeneratedMenuWidget.GeneratedMenu->bShouldCleanupContextOnDestroy = true;
|
|
// Copy native properties that serialize does not
|
|
GeneratedMenuWidget.GeneratedMenu->Context = GeneratedMenu->Context;
|
|
GeneratedMenuWidget.GeneratedMenu->StyleSetName = GeneratedMenu->StyleSetName;
|
|
GeneratedMenuWidget.GeneratedMenu->StyleName = GeneratedMenu->StyleName;
|
|
|
|
if (GeneratedWidget)
|
|
{
|
|
GeneratedMenuWidget.Widget = GeneratedWidget;
|
|
return GeneratedWidget.ToSharedRef();
|
|
}
|
|
else
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
void UToolMenus::AssignSetTimerForNextTickDelegate(const FSimpleDelegate& InDelegate)
|
|
{
|
|
SetTimerForNextTickDelegate = InDelegate;
|
|
}
|
|
|
|
void UToolMenus::SetNextTickTimer()
|
|
{
|
|
if (!bNextTickTimerIsSet)
|
|
{
|
|
if (SetTimerForNextTickDelegate.IsBound())
|
|
{
|
|
bNextTickTimerIsSet = true;
|
|
SetTimerForNextTickDelegate.Execute();
|
|
}
|
|
}
|
|
}
|
|
|
|
void UToolMenus::CleanupStaleWidgetsNextTick(bool bGarbageCollect)
|
|
{
|
|
bCleanupStaleWidgetsNextTick = true;
|
|
|
|
if (bGarbageCollect)
|
|
{
|
|
bCleanupStaleWidgetsNextTickGC = true;
|
|
}
|
|
|
|
SetNextTickTimer();
|
|
}
|
|
|
|
void UToolMenus::RefreshAllWidgets()
|
|
{
|
|
if (!bSuppressRefreshWidgetsRequests)
|
|
{
|
|
bRefreshWidgetsNextTick = true;
|
|
SetNextTickTimer();
|
|
}
|
|
}
|
|
|
|
void UToolMenus::HandleNextTick()
|
|
{
|
|
if (bCleanupStaleWidgetsNextTick || bRefreshWidgetsNextTick)
|
|
{
|
|
CleanupStaleWidgets();
|
|
bCleanupStaleWidgetsNextTick = false;
|
|
bCleanupStaleWidgetsNextTickGC = false;
|
|
|
|
if (bRefreshWidgetsNextTick)
|
|
{
|
|
TGuardValue<bool> SuppressRefreshWidgetsRequestsGuard(bSuppressRefreshWidgetsRequests, true);
|
|
|
|
// Copy before enumerate because modified inside RefreshMenuWidget
|
|
TMap<FName, TSharedPtr<FGeneratedToolMenuWidgets>> GeneratedMenuWidgetsCopy = GeneratedMenuWidgets;
|
|
for (TPair<FName, TSharedPtr<FGeneratedToolMenuWidgets>>& WidgetsForMenuNameIt : GeneratedMenuWidgetsCopy)
|
|
{
|
|
// Copy before enumerate because modified inside RefreshMenuWidget
|
|
TArray<TSharedPtr<FGeneratedToolMenuWidget>> InstancesCopy = WidgetsForMenuNameIt.Value->Instances;
|
|
for (TSharedPtr<FGeneratedToolMenuWidget>& Instance : InstancesCopy)
|
|
{
|
|
if (Instance->Widget.IsValid())
|
|
{
|
|
RefreshMenuWidget(WidgetsForMenuNameIt.Key, *Instance);
|
|
}
|
|
}
|
|
}
|
|
|
|
bRefreshWidgetsNextTick = false;
|
|
}
|
|
}
|
|
|
|
bNextTickTimerIsSet = false;
|
|
}
|
|
|
|
void UToolMenus::CleanupStaleWidgets()
|
|
{
|
|
bool bModified = false;
|
|
for (auto WidgetsForMenuNameIt = GeneratedMenuWidgets.CreateIterator(); WidgetsForMenuNameIt; ++WidgetsForMenuNameIt)
|
|
{
|
|
TSharedPtr<FGeneratedToolMenuWidgets>& WidgetsForMenuName = WidgetsForMenuNameIt->Value;
|
|
|
|
for (auto Instance = WidgetsForMenuName->Instances.CreateIterator(); Instance; ++Instance)
|
|
{
|
|
if (!(*Instance)->Widget.IsValid())
|
|
{
|
|
bModified = true;
|
|
Instance.RemoveCurrent();
|
|
}
|
|
}
|
|
|
|
if (WidgetsForMenuName->Instances.Num() == 0)
|
|
{
|
|
bModified = true;
|
|
WidgetsForMenuNameIt.RemoveCurrent();
|
|
}
|
|
}
|
|
|
|
if (bModified && bCleanupStaleWidgetsNextTickGC && !IsAsyncLoading())
|
|
{
|
|
CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS);
|
|
}
|
|
}
|
|
|
|
bool UToolMenus::RefreshMenuWidget(const FName InName)
|
|
{
|
|
bool bRefreshedAnyWidget = false;
|
|
|
|
// Copy the TSharedPtr because RefreshMenuWidget can modify GeneratedMenuWidgets and delete any memory we point to
|
|
if (TSharedPtr<FGeneratedToolMenuWidgets> WidgetsForMenuName = GeneratedMenuWidgets.FindRef(InName); WidgetsForMenuName.IsValid())
|
|
{
|
|
// Copy before enumerate because entries can be added to this array inside RefreshMenuWidget
|
|
TArray<TSharedPtr<FGeneratedToolMenuWidget>> InstancesCopy = WidgetsForMenuName->Instances;
|
|
for (TSharedPtr<FGeneratedToolMenuWidget>& Instance : InstancesCopy)
|
|
{
|
|
if (RefreshMenuWidget(InName, *Instance))
|
|
{
|
|
bRefreshedAnyWidget = true;
|
|
}
|
|
else
|
|
{
|
|
// Remove from original instead of InstancesCopy
|
|
WidgetsForMenuName->Instances.Remove(Instance);
|
|
}
|
|
}
|
|
}
|
|
|
|
return bRefreshedAnyWidget;
|
|
}
|
|
|
|
bool UToolMenus::RefreshMenuWidget(const FName InName, FGeneratedToolMenuWidget& GeneratedMenuWidget)
|
|
{
|
|
if (!GeneratedMenuWidget.Widget.IsValid())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Regenerate menu from database
|
|
GeneratedMenuWidget.GeneratedMenu->bShouldCleanupContextOnDestroy = false; // The new menu will do this
|
|
|
|
// GeneratedMenuWidget.GeneratedMenu is a copy of the original menu, so we also need to make sure the original menu does not clean up its context
|
|
if(UToolMenu* OriginalMenu = GeneratedMenuWidget.OriginalMenu.Get())
|
|
{
|
|
OriginalMenu->bShouldCleanupContextOnDestroy = false;
|
|
}
|
|
|
|
UToolMenu* GeneratedMenu = GenerateMenu(InName, GeneratedMenuWidget.GeneratedMenu->Context);
|
|
GeneratedMenuWidget.GeneratedMenu = GeneratedMenu;
|
|
|
|
const ISlateStyle* StyleSetNotNull = GeneratedMenu->GetStyleSet();
|
|
const bool bHadStyleSet = StyleSetNotNull != nullptr;
|
|
if (!bHadStyleSet)
|
|
{
|
|
// Avoid crash when style sets are unregistered/deleted, GetStyleSet() will report an ensure when that happens but return null and menu builders crash when passed null StyleSet.
|
|
StyleSetNotNull = &FCoreStyle::Get();
|
|
}
|
|
|
|
// 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, StyleSetNotNull, GeneratedMenu->bSearchable);
|
|
MenuBuilder.SetExtendersEnabled(GeneratedMenu->bExtendersEnabled);
|
|
|
|
if (bHadStyleSet && GeneratedMenu->StyleName != NAME_None)
|
|
{
|
|
MenuBuilder.SetStyle(StyleSetNotNull, GeneratedMenu->StyleName);
|
|
}
|
|
|
|
PopulateMenuBuilder(MenuBuilder, GeneratedMenu);
|
|
MultiBoxWidget->SetMultiBox(MenuBuilder.GetMultiBox());
|
|
}
|
|
else if (GeneratedMenu->MenuType == EMultiBoxType::MenuBar)
|
|
{
|
|
FMenuBarBuilder MenuBarBuilder(GeneratedMenu->Context.CommandList, GeneratedMenu->Context.GetAllExtenders(), StyleSetNotNull);
|
|
MenuBarBuilder.SetExtendersEnabled(GeneratedMenu->bExtendersEnabled);
|
|
|
|
if (bHadStyleSet && GeneratedMenu->StyleName != NAME_None)
|
|
{
|
|
MenuBarBuilder.SetStyle(StyleSetNotNull, GeneratedMenu->StyleName);
|
|
}
|
|
|
|
PopulateMenuBarBuilder(MenuBarBuilder, GeneratedMenu);
|
|
MultiBoxWidget->SetMultiBox(MenuBarBuilder.GetMultiBox());
|
|
}
|
|
else if (GeneratedMenu->MenuType == EMultiBoxType::ToolBar || GeneratedMenu->MenuType == EMultiBoxType::VerticalToolBar || GeneratedMenu->MenuType == EMultiBoxType::UniformToolBar || GeneratedMenu->MenuType == EMultiBoxType::SlimHorizontalToolBar || GeneratedMenu->MenuType == EMultiBoxType::SlimWrappingToolBar)
|
|
{
|
|
FToolBarBuilder ToolbarBuilder(GeneratedMenu->MenuType, GeneratedMenu->Context.CommandList, GeneratedMenu->MenuName, GeneratedMenu->Context.GetAllExtenders(), GeneratedMenu->bToolBarForceSmallIcons);
|
|
ToolbarBuilder.SetExtendersEnabled(GeneratedMenu->bExtendersEnabled);
|
|
ToolbarBuilder.SetIsFocusable(GeneratedMenu->bToolBarIsFocusable);
|
|
ToolbarBuilder.SetAllowWrapButton(GeneratedMenu->bAllowToolBarWrapButton);
|
|
|
|
if (bHadStyleSet && GeneratedMenu->StyleName != NAME_None)
|
|
{
|
|
ToolbarBuilder.SetStyle(StyleSetNotNull, GeneratedMenu->StyleName);
|
|
}
|
|
|
|
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 GenerateMenuFromHierarchy(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;
|
|
Found->bIsRegistering = true;
|
|
for (FToolMenuSection& Section : Found->Sections)
|
|
{
|
|
Section.bIsRegistering = Found->bIsRegistering;
|
|
}
|
|
}
|
|
else if (bWarnIfAlreadyRegistered)
|
|
{
|
|
UE_LOG(LogToolMenus, Warning, TEXT("Menu already registered : %s"), *InName.ToString());
|
|
}
|
|
|
|
return Found;
|
|
}
|
|
|
|
UToolMenu* ToolMenu = NewToolMenuObject(FName(TEXT("RegisteredMenu")), InName);
|
|
ToolMenu->InitMenu(CurrentOwner(), InName, InParent, InType);
|
|
ToolMenu->bRegistered = true;
|
|
ToolMenu->bIsRegistering = true;
|
|
Menus.Add(InName, ToolMenu);
|
|
return ToolMenu;
|
|
}
|
|
|
|
UToolMenu* UToolMenus::ExtendMenu(const FName InName)
|
|
{
|
|
if (UToolMenu* Found = FindMenu(InName))
|
|
{
|
|
Found->bIsRegistering = false;
|
|
for (FToolMenuSection& Section : Found->Sections)
|
|
{
|
|
Section.bIsRegistering = Found->bIsRegistering;
|
|
}
|
|
|
|
// Refresh all widgets because this could be child of another menu being displayed
|
|
RefreshAllWidgets();
|
|
|
|
return Found;
|
|
}
|
|
|
|
UToolMenu* ToolMenu = NewToolMenuObject(FName(TEXT("RegisteredMenu")), InName);
|
|
ToolMenu->bRegistered = false;
|
|
ToolMenu->bIsRegistering = false;
|
|
Menus.Add(InName, ToolMenu);
|
|
return ToolMenu;
|
|
}
|
|
|
|
UToolMenu* UToolMenus::NewToolMenuObject(const FName NewBaseName, const FName InMenuName)
|
|
{
|
|
FName UniqueObjectName = MakeUniqueObjectName(this, UToolMenus::StaticClass(), NewBaseName);
|
|
UToolMenu* Result = NewObject<UToolMenu>(this, UniqueObjectName);
|
|
Result->MenuName = InMenuName;
|
|
return Result;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
bool UToolMenus::RemoveMenuEntryObject(UToolMenuEntryScript* MenuEntryObject)
|
|
{
|
|
if (UToolMenu* Menu = UToolMenus::Get()->FindMenu(MenuEntryObject->Data.Menu))
|
|
{
|
|
Menu->RemoveMenuEntryObject(MenuEntryObject);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
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())
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool bNeedsRefresh = false;
|
|
|
|
for (const TPair<FName, TObjectPtr<UToolMenu>>& Pair : Menus)
|
|
{
|
|
UToolMenu* Menu = Pair.Value;
|
|
for (int32 SectionIndex = Menu->Sections.Num() - 1; SectionIndex >= 0; --SectionIndex)
|
|
{
|
|
FToolMenuSection& Section = Menu->Sections[SectionIndex];
|
|
if (Section.RemoveEntriesByOwner(InOwner) > 0)
|
|
{
|
|
bNeedsRefresh = true;
|
|
}
|
|
|
|
if (Section.Owner == InOwner)
|
|
{
|
|
if (Section.Construct.IsBound())
|
|
{
|
|
Section.Construct = FNewSectionConstructChoice();
|
|
bNeedsRefresh = true;
|
|
}
|
|
|
|
if (Section.ToolMenuSectionDynamic)
|
|
{
|
|
Section.ToolMenuSectionDynamic = nullptr;
|
|
bNeedsRefresh = true;
|
|
}
|
|
|
|
if (Section.Blocks.Num() == 0)
|
|
{
|
|
Menu->Sections.RemoveAt(SectionIndex, EAllowShrinking::No);
|
|
bNeedsRefresh = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Refresh any widgets that are currently displayed to the user
|
|
if (bNeedsRefresh)
|
|
{
|
|
RefreshAllWidgets();
|
|
}
|
|
}
|
|
|
|
void UToolMenus::UnregisterRuntimeMenuCustomizationOwner(const FName InOwnerName)
|
|
{
|
|
if (InOwnerName.IsNone())
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool bNeedsRefresh = false;
|
|
for (FCustomizedToolMenu& CustomizedToolMenu : RuntimeCustomizedMenus)
|
|
{
|
|
if (CustomizedToolMenu.MenuPermissions.UnregisterOwner(InOwnerName))
|
|
{
|
|
bNeedsRefresh = true;
|
|
}
|
|
|
|
if (CustomizedToolMenu.SuppressExtenders.Remove(InOwnerName) > 0)
|
|
{
|
|
bNeedsRefresh = true;
|
|
}
|
|
}
|
|
|
|
// Refresh any widgets that are currently displayed to the user
|
|
if (bNeedsRefresh)
|
|
{
|
|
RefreshAllWidgets();
|
|
}
|
|
}
|
|
|
|
void UToolMenus::UnregisterRuntimeMenuProfileOwner(const FName InOwnerName)
|
|
{
|
|
if (InOwnerName.IsNone())
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool bNeedsRefresh = false;
|
|
|
|
// Loop through all menus with profiles
|
|
for (TPair<FName, FToolMenuProfileMap>& MenusWithProfiles : RuntimeMenuProfiles)
|
|
{
|
|
// Loop through all profiles for a given menu
|
|
for (TPair<FName, FToolMenuProfile>& MenuProfile : MenusWithProfiles.Value.MenuProfiles)
|
|
{
|
|
if (MenuProfile.Value.MenuPermissions.UnregisterOwner(InOwnerName))
|
|
{
|
|
bNeedsRefresh = true;
|
|
}
|
|
|
|
if (MenuProfile.Value.SuppressExtenders.Remove(InOwnerName) > 0)
|
|
{
|
|
bNeedsRefresh = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Refresh any widgets that are currently displayed to the user
|
|
if (bNeedsRefresh)
|
|
{
|
|
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(EAllowShrinking::No);
|
|
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);
|
|
}
|
|
|
|
FDelegateHandle UToolMenus::RegisterStartupCallback(const FSimpleMulticastDelegate::FDelegate& InDelegate)
|
|
{
|
|
if (IsToolMenuUIEnabled() && UToolMenus::TryGet())
|
|
{
|
|
// Call immediately if systems are initialized
|
|
InDelegate.Execute();
|
|
}
|
|
else
|
|
{
|
|
// Defer call to occur after systems are initialized (slate and menus)
|
|
FDelegateHandle Result = StartupCallbacks.Add(InDelegate);
|
|
|
|
if (!InternalStartupCallbackHandle.IsSet())
|
|
{
|
|
InternalStartupCallbackHandle = FCoreDelegates::OnPostEngineInit.Add(FSimpleMulticastDelegate::FDelegate::CreateStatic(&UToolMenus::PrivateStartupCallback));
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
return FDelegateHandle();
|
|
}
|
|
|
|
void UToolMenus::UnRegisterStartupCallback(FDelegateUserObjectConst UserPointer)
|
|
{
|
|
StartupCallbacks.RemoveAll(UserPointer);
|
|
}
|
|
|
|
void UToolMenus::UnRegisterStartupCallback(FDelegateHandle InHandle)
|
|
{
|
|
StartupCallbacks.Remove(InHandle);
|
|
}
|
|
|
|
void UToolMenus::PrivateStartupCallback()
|
|
{
|
|
UnregisterPrivateStartupCallback();
|
|
|
|
if (IsToolMenuUIEnabled() && UToolMenus::TryGet())
|
|
{
|
|
StartupCallbacks.Broadcast();
|
|
StartupCallbacks.Clear();
|
|
}
|
|
}
|
|
|
|
void UToolMenus::UnregisterPrivateStartupCallback()
|
|
{
|
|
if (InternalStartupCallbackHandle.IsSet())
|
|
{
|
|
FDelegateHandle& Handle = InternalStartupCallbackHandle.GetValue();
|
|
if (Handle.IsValid())
|
|
{
|
|
FCoreDelegates::OnPostEngineInit.Remove(Handle);
|
|
Handle.Reset();
|
|
}
|
|
}
|
|
}
|
|
|
|
void UToolMenus::SaveCustomizations()
|
|
{
|
|
SaveConfig();
|
|
}
|
|
|
|
void UToolMenus::RemoveAllCustomizations()
|
|
{
|
|
CustomizedMenus.Reset();
|
|
}
|
|
|
|
namespace UE::ToolMenus
|
|
{
|
|
|
|
FToolMenuTestInstanceScoped::FToolMenuTestInstanceScoped()
|
|
{
|
|
ScopedInstance = Private::CreateToolMenusInstance();
|
|
|
|
PreviousInstance = UToolMenus::Get();
|
|
|
|
UToolMenus::Singleton = ScopedInstance;
|
|
}
|
|
|
|
FToolMenuTestInstanceScoped::~FToolMenuTestInstanceScoped()
|
|
{
|
|
// Reinstate the previous singleton.
|
|
UToolMenus::Singleton = PreviousInstance;
|
|
|
|
// Remove our scoped instance from the root after reinstating the previous instance to not risk our scoped instance
|
|
// from destroying and taking down the whole ToolMenus system with it.
|
|
ScopedInstance->RemoveFromRoot();
|
|
}
|
|
|
|
} // namespace UE::ToolMenus
|
|
|
|
#undef LOCTEXT_NAMESPACE
|
|
|