Files
UnrealEngineUWP/Engine/Source/Runtime/Slate/Private/Framework/Application/MenuStack.cpp
Justin Sargent 3fec0111ab Copying //UE4/Portal-Staging to Dev-Main (//UE4/Dev-Main) (Source: //Portal/Main @ 3352026)
#lockdown Nick.Penwarden

==========================
MAJOR FEATURES + CHANGES
==========================

Change 3351920 on 2017/03/17 by Leigh.Swift

	#jira OPP-6870: [Crash] Assertion failed: ctx->ReadOffset + length <= (uint32)ctx->CompressedData.Num() [File:D:\Build\++Portal+Release-Live+Compile\Sync\Engine\Source\Runtime\ImageWrapper\Private\PngImageWrapper.cpp] [Line: 420]
	Instead of asserting for an incorrect data size for png image being loaded, set an error on the wrapper object.
	This will result in the slate brush rendering as white box, and errors going to the log which include the png resource path.

Change 3350561 on 2017/03/16 by Justin.Sargent

	Updated the Slate standalone D3D renderer to be more fault tolerant.
	Added new HasLostDevice() function to SlateRenderer.
	If the launcher detects that it has lost the device context it will attempt to perform a full rebuild of the UI and Presentation layers, but also a full reboot of slate application and the renderer.
	The launcher will attempt to re-establish the device context for 5 minutes if not interrupted by the user. If the user interacts with the launcher while attempting to re-establish the device context it will instead pop up a system dialog explaining it is having issues with the video card and then will close the application.

Change 3341299 on 2017/03/10 by Richard.Fawcett

	Validate JSON files as we're pushing to EMS

	Added a flexible validation framework to the system files (EMS) sync operation.
	Implemented a JSON validator which by default will validate .json files.

	Adds a "ValidationRules" property to the EMS sync config file to allow each product to define its own regex patterns for filenames which should be validated with each validator.
	Configured launcher's EMS to validate .product, .v?product, .layout, .sdmeta and .panel files as JSON.

	The great news is that this validation actually discovered a minor syntactical issue with Wex.v2product during testing, which is also fixed with this submission.  \o/

	#epicfriday

Change 3336908 on 2017/03/08 by Leigh.Swift

	#jira OPP-5126: All disk size checks for BPS installations should be handled internally to BPS, the Portal code should not need to check this and can only do so less accurately.
	Removing disk space checks from portal code which block installers from running. NB: There is still a check in selective download code which provides tooltip information only.
	Moving BuildPatchInstaller disk size check to the file constructor, which is the class that most accurately knows the required amount of space. The disk check now occurs after resume and just before we start to write data.
	A secondary disk check is also made if a file fails to construct so that we can detect problems caused by change in available disk space.
	Disk space error message extended to include useful information for the user.

Change 3323366 on 2017/02/27 by Richard.Fawcett

	Fix reference to Newtonsoft in Publishing.Automation.

Change 3323205 on 2017/02/27 by Wes.Fudala

	Adding language support to the windows installer.
	Significantly expandes OneSky upload functionality.
	OPP-5438 Launcher installer should support the same language set supported by the launcher.

Change 3316926 on 2017/02/22 by Richard.Fawcett

	Prevent Amazon S3 download glitches from issuing warnings until half of the maximum retries have been attempted.

	In practice, when we download thousands of files, we _do_ get failures which need to be retried.  This means that pretty much all jobs result in warnings, which isn't appropriate. This should turn jobs green again, and only warn us when things look unusual.

	#jira OPP-6607

Change 3315996 on 2017/02/21 by Justin.Sargent

	Incrementing Portal version number

	I'm incrementing this earlier than normal due to a need to depend on a new version number for EMS layout change versioning.

Change 3312760 on 2017/02/20 by Wes.Fudala

	Users can now select desired environment/region from the UI.
	New login screen.
	Adds slid out settings menu to login screen.
	Adds region and language selection to the settings menu.
	Adds support for PortalRegions ini.
	Adds DefaultPortalRegions ini.
	Adds RegionRepository, RegionPublisher, RegionSelectService, and RegionIdentificationService.
	Adds region select option in debugtools general section.
	Adds RegionSelectService unit test with associated mocks.
	Changes the way all backend connections are configured so that values are now pulled from config files.
	Renames product region selector files to avoid some confusion with portal region files.
	Updated EmsConfigUpdater and HotfixManager so they support optional overwrite and save of ini files.
	Region publisher now restricts regions that require permissions in shipping builds.
	Fixes a bug causing items to get stuck in the download queue UI after we failed to obtain a manifest when reaching out the the backend.

	#jira OPP-6121,  OPP-5809

Change 3311664 on 2017/02/20 by Andrew.Brown

	Added GetTypeHash support for FDelegateHandle

Change 3311505 on 2017/02/20 by Richard.Fawcett

	Rename/move file(s)

Change 3309004 on 2017/02/17 by Chad.Garyet

	adding in additional platforms for the BuildPlugin commandline

Change 3299188 on 2017/02/13 by Leigh.Swift

	#jira OPP-6711: [CRASH] Assertion failed: IsComplete()
	Race condition in portal hack allowed an installer to attempt to execute complete delegate before being complete.

Change 3294197 on 2017/02/09 by Richard.Fawcett

	Adding ValidPrereqIds.json

	This is needed now because the script to enforce correct prereq ids reaches out to Perforce to access this file!

	#jira OPP-6583

Change 3294059 on 2017/02/09 by Richard.Fawcett

	Fix comment on PostBuild parameter to reference correct name of "Manifest" property.

Change 3293377 on 2017/02/08 by Richard.Fawcett

	Remove need for a metadata file containing name of generated manifest when using randomized manifest filenames.

Change 3282865 on 2017/02/02 by Richard.Fawcett

	Simplify params to BuildGraph's ChunkTask / PostBuildTask

	Also, downgrade warnings to simple log messages when falling back to legacy manifest filename construction to ease transition into randomized manifest filenames for game teams.
	#jira OPP-6435

Change 3282809 on 2017/02/02 by Leigh.Swift

	#jira OPP-6564: BPT will crash if the FileIgnoreList input instructs the build streamer to ignore every build file.
	A race condition for getting to the first scanner creation code, vs the build stream exiting with no data. If the former wins, a scanner will be created without enough data to scan.
	Scanners are now no longer created if the buildstream provided no data.

Change 3280848 on 2017/02/01 by Leigh.Swift

	#jira OPP-3864: BuildPatchServices will log a FATAL error on shutdown even if it's fine to be shutting down.
	When BPS is shutdown, it will only set error states and cancellation logic if any installers are actually created.

Change 3280839 on 2017/02/01 by Leigh.Swift

	Fixing whitespace damage incoming from Dev-Staging

Change 3280820 on 2017/02/01 by Andrew.Brown

	Copying //Portal/Dev-Main-Staging to Main (//Portal/Main)

Change 3280797 on 2017/02/01 by Leigh.Swift

	#jira OPP-6649: BPS sends undocumented analytics events.
	Adding documentation for the following events:
	Patcher.Error.Download
	Patcher.Warning.ChunkAborted
	Patcher.Error.Cache
	Patcher.Error.Construction
	Patcher.Error.Prerequisites

Change 3278887 on 2017/01/31 by Richard.Fawcett

	Downgrade cleanup warnings to normal log output.

	The conditions which used to trigger these warnings are now considered to be normal behavior, in a world in which we carry out cross-app game promotions.

	This results in a perma-yellow state for cleanup, which is unhelpful.

	#nojira

Change 3278738 on 2017/01/31 by Richard.Fawcett

	Tweak Conan launch parameters

Change 3277066 on 2017/01/30 by Richard.Fawcett

	Remove temporary code which cleans up P:\Builds\UnrealEngineLauncher\BuildGraph, as the location no longer exists.

Change 3274907 on 2017/01/27 by Leigh.Swift

	#jira OPP-6615: Receiving a whisper while in game may minimize game client.
	Refactoring SWindow ActivateOnFirstShown bool to be ActivationPolicy to give more accurate control.
	This also allows fixing of misuses of previous ActivateOnFirstShown variables in the implementations, which appear to mostly be interpreting it as 'AlwaysActivate'.
	The upgrade path is therefore ActivateOnFirstShown true/false becomes ActivationPolicy Always/Never.
	Moving initial minimize and maximise logic for FWindowsWindow into the Show() call on first show to gain control of activation and respect the provided policy.
	Refactoring existing uses to use the new variables/functions instead.
	The refactor of existing code is focused on preserving current always activate behaviour as opposed to changing behaviour to actually only activate on first show.

Change 3273466 on 2017/01/26 by Alex.Fennell

	New build of OpenSSL libraries    #JIRA OPP-6408
	PriceEngine configuration and fixes for bugs it introduced.

Change 3268045 on 2017/01/23 by Richard.Fawcett

	Re-adding Funcom folk to Conan chunk notification emails

Change 3267709 on 2017/01/23 by Richard.Fawcett

	Fix launch arguments for Conan Exiles editor.
	Temporarily remove FunCom recipients from notification list to avoid spamming.

Change 3265774 on 2017/01/20 by Chad.Garyet

	Merge of Engine/Build/Buildfarm over to //Portal from Dev-Build

Change 3264674 on 2017/01/19 by Alex.Fennell

	On demand catalog requests

Change 3263654 on 2017/01/19 by Leigh.Swift

	#jira OPP6562: Support looking up tagging and sdmeta info and using it in build diff output
	Adding tag use understanding to the manifest diff tool of BPT.
	Adding Selective Download feature support to PPT for it's diff tool, making use of portal's metadata for the feature.

Change 3263623 on 2017/01/19 by Richard.Fawcett

	Fix issue where ManifestFilename is not always available at post build time.

	#jira OPP-6606

Change 3262013 on 2017/01/18 by Richard.Fawcett

	Remote potential for success email being sent on third party chunk failure

Change 3261914 on 2017/01/18 by Richard.Fawcett

	Fix for user content generation job not specifying a manifest filename.

Change 3261800 on 2017/01/18 by Richard.Fawcett

	Implement streaming S3 downloads to disk, rather than just to memory

	This is needed because C# has a 2 billion maximum array dimension, so files > 2GB can't be downloaded using the existing code.

Change 3261675 on 2017/01/18 by Richard.Fawcett

	Support for overriding, or generating randomized unique manifest filenames to avoid automated harvesting from CDN

	BuildGraph's ChunkTask takes three new parameters ...
	* ManifestFilename (string)        - The filename of the manifest to produce. If omitted, the value of RandomizeManifestFilename will determine how the manifest filename is determined.
	* RandomizeManifestFilename (bool) - If true, we'll generate a random, unique manifest filename. If false (default), we'll use legacy behavior of combining app name and build version.
	* LocalManifestDir (string)        - Required if RandomizedManifestFilename is true. This directory will receive local copies of any manifest file produced, and a metadata file containing the name of the most recently produced manifest

	BuildGraph's PostBuildTask takes two new parameters ...
	* ManifestFilename (string)        - The filename of the manifest to post. If omitted, we'll use the value from the metadat file in LocalManifestDir is this is set, otherwise use legacy behavior.
	* LocalManifestDir (string)        - A directory containing local copies of manifest files, along with a metadata file containing the name of the manifest file produced by the most recent ChunkTask operation.

	Support added to the launcher build script's to use the new parameters to randomize its manifest filename, and post the randomized filename to MCP.

	Use of a contructor of BuildPatchToolStagingInfo which does not specify a manifest filename is now considered deprecated, and will output a warning.

	Remove requirement of having a BuildPatchToolStagingInfo when performing a chunking operation, instead just passing in the specific values we need from it as parameters in their own right.

	Remove support for non-chunk based manifests from C# wrapper, as these are no longer supported in BuildPatchTool itself.

	#jira OPP-6432

Change 3261647 on 2017/01/18 by Leigh.Swift

	Adding some cleanup to the end of some BPT functional tests so that they do not affect proceeding tests and cause red-herring warning output.

Change 3261639 on 2017/01/18 by Richard.Fawcett

	Update app name of Conan to ConanExiles to match back-end catalog.
	Fix Conan launch exe and args so that launcher can detect when product is running.  Was previously using a batch file which terminates after launching editor.

Change 3258815 on 2017/01/16 by Wes.Fudala

	UTM and product info will be parsed from installer name and passed to the launcher.  UTM info will be passed along as part of all analytics events.
	#jira OPP-6404: Add user funnel tracking

Change 3258809 on 2017/01/16 by Wes.Fudala

	Back out changelist 3258800.
	Backing out changes that were intended to be made in a different stream.

Change 3258800 on 2017/01/16 by Wes.Fudala

	App version is now also appended to user agent string.

Change 3256999 on 2017/01/13 by Richard.Fawcett

	Fix issue where JSON file included in Publishing csproj is not reliably copied to output folder on build farm.

Change 3256941 on 2017/01/13 by Richard.Fawcett

	Move configuration for Third Party build pipeline out of code and into its own configuration file.

	#epicfriday

Change 3255072 on 2017/01/12 by Richard.Fawcett

	Add additional logging around multithreaded upload of files to S3.
	Fix bug ensuring that the failure of any single part of multi-part upload results in the whole file being failed.

	#jira OPP-6392

Change 3253672 on 2017/01/11 by Richard.Fawcett

	Add support for third-party Conan editor.

	Alter third party process so it doesn't crash if version.txt doesn't already exist in the third party S3 bucket, to allow us to setup in advance of third party publishing their first version.

Change 3251901 on 2017/01/10 by Barnabas.McManners

	Compile fix on mac,  fix for hidden method in AutomationTest define. Without this GoogleMock.spec.cpp wont compile on mac.
	#nojira
	#ReviewedBy Leigh.Swift

Change 3250907 on 2017/01/09 by Justin.Sargent

	Changed the automation controller to uses a non-zero exit code when performing a 'quit' command if tests failed.

Change 3245328 on 2017/01/03 by Justin.Sargent

	Enabling the logic to lowercase all C++ members exposed to javascript.
	Added additional to-lowering behavior to UObject binding.
	#jira OPP-6494

Change 3240667 on 2016/12/20 by Andrew.Brown

	Copying //Tasks/Portal/Dev-OPP-6109-DedicatedServer to Dev-Main (//Portal/Dev-Main)

Change 3236972 on 2016/12/15 by Bob.Ferreira

	Updating compliation changes for AutomationDriver

Change 3236567 on 2016/12/15 by Richard.Fawcett

	Ensure that third party product chunking uses latest CL across our P4 depot in its version number.

Change 3236188 on 2016/12/15 by Richard.Fawcett

	Combine all launcher purchases into single workflow using the new quickPurchase API call as the initial request.

	#jira OPP-6257

Change 3231134 on 2016/12/12 by Alex.Fennell

	Improving fail case handling for the waiting room service

	#jira OPP-5648

Change 3228514 on 2016/12/09 by Richard.Fawcett

	Change filetype

Change 3227080 on 2016/12/08 by Barnabas.McManners

	Merging CL 3226840 from Dev Editor

	Fixing a bug in FText formatting where it would ignore the rebuild and Rebuild as Source arguments for the format string itself

	#jira OPP-6485

Change 3219810 on 2016/12/02 by Ben.Marsh

	UAT: Fix unzip output being completely discarded. Switch it to just be verbose instead.

Change 3219602 on 2016/12/02 by Ben.Marsh

	Add the -q (quiet) option to the Mac unzip command, since it's creating too much log output to be useful.

[CL 3355309 by Justin Sargent in Main branch]
2017-03-20 18:49:23 -04:00

879 lines
29 KiB
C++

// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
#include "Framework/Application/MenuStack.h"
#include "Layout/LayoutUtils.h"
#include "Layout/WidgetPath.h"
#include "Framework/Application/SlateApplication.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Layout/SPopup.h"
#include "Framework/Application/Menu.h"
#define LOCTEXT_NAMESPACE "MenuStack"
namespace FMenuStackDefs
{
/** Maximum size of menus as a fraction of the work area height */
const float MaxMenuScreenHeightFraction = 0.8f;
const float AnimationDuration = 0.15f;
}
/** Overlay widget class used to hold menu contents and display them on top of the current window */
class SMenuPanel : public SOverlay
{
public:
SLATE_BEGIN_ARGS(SMenuPanel)
{
_Visibility = EVisibility::SelfHitTestInvisible;
}
SLATE_END_ARGS()
void Construct(const FArguments& InArgs)
{
SOverlay::Construct(SOverlay::FArguments());
}
void PushMenu(TSharedRef<FMenuBase> InMenu, const FVector2D& InLocation)
{
check(InMenu->GetContent().IsValid());
TSharedPtr<SWindow> ParentWindow = InMenu->GetParentWindow();
check(ParentWindow.IsValid());
// Transform InLocation into a position local to this panel (assumes the panel is in an overlay that covers the whole of the panel window)
FVector2D PanelInScreen = ParentWindow->GetRectInScreen().GetTopLeft();
FVector2D PanelInWindow = ParentWindow->GetLocalToScreenTransform().Inverse().TransformPoint(PanelInScreen);
FVector2D LocationInWindow = ParentWindow->GetLocalToScreenTransform().Inverse().TransformPoint(InLocation);
FVector2D LocationInPanel = LocationInWindow - PanelInWindow;
// Add the new menu into a slot on this panel and set the padding so that its position is correct
AddSlot()
.HAlign(HAlign_Left)
.VAlign(VAlign_Top)
.Padding(LocationInPanel.X, LocationInPanel.Y, 0, 0)
[
InMenu->GetContent().ToSharedRef()
];
// Make sure that the menu will remove itself from the panel when dismissed
InMenu->GetOnMenuDismissed().AddSP(this, &SMenuPanel::OnMenuClosed);
}
void OnMenuClosed(TSharedRef<IMenu> InMenu)
{
RemoveSlot(InMenu->GetContent().ToSharedRef());
}
};
namespace
{
/** Widget that wraps any menu created in FMenuStack to provide default key handling, focus tracking and helps us spot menus in widget paths */
DECLARE_DELEGATE_RetVal_OneParam(FReply, FOnKeyDown, FKey)
DECLARE_DELEGATE_OneParam(FOnMenuLostFocus, const FWidgetPath&)
class SMenuContentWrapper : public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS(SMenuContentWrapper)
: _MenuContent()
, _OnKeyDown()
, _OptionalMinMenuWidth()
, _OptionalMinMenuHeight()
{}
SLATE_DEFAULT_SLOT(FArguments, MenuContent)
SLATE_EVENT(FOnKeyDown, OnKeyDown)
SLATE_EVENT(FOnMenuLostFocus, OnMenuLostFocus)
SLATE_ARGUMENT(FOptionalSize, OptionalMinMenuWidth)
SLATE_ARGUMENT(FOptionalSize, OptionalMinMenuHeight)
SLATE_END_ARGS()
/** Construct this widget */
void Construct(const FArguments& InArgs)
{
// The visibility of the content wrapper should match that of the provided content
Visibility = AccessWidgetVisibilityAttribute(InArgs._MenuContent.Widget);
OnKeyDownDelegate = InArgs._OnKeyDown;
OnMenuLostFocus = InArgs._OnMenuLostFocus;
ChildSlot
[
SNew(SBox)
.MinDesiredWidth(InArgs._OptionalMinMenuWidth)
.MaxDesiredHeight(InArgs._OptionalMinMenuHeight)
[
InArgs._MenuContent.Widget
]
];
}
virtual void OnFocusChanging(const FWeakWidgetPath& PreviousFocusPath, const FWidgetPath& NewWidgetPath, const FFocusEvent& InFocusEvent) override
{
// if focus changed and this menu content had focus (or one of its children did) then inform the stack via the OnMenuLostFocus event
if (OnMenuLostFocus.IsBound() && PreviousFocusPath.ContainsWidget(AsShared()))
{
return OnMenuLostFocus.Execute(NewWidgetPath);
}
}
private:
/** This widget must support keyboard focus */
virtual bool SupportsKeyboardFocus() const override
{
return true;
}
virtual FReply OnKeyDown( const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent ) override
{
if (OnKeyDownDelegate.IsBound())
{
return OnKeyDownDelegate.Execute(InKeyEvent.GetKey());
}
return FReply::Unhandled();
}
/** Delegate to forward keys down events on the menu */
FOnKeyDown OnKeyDownDelegate;
/** Delegate to inform the stack that a menu has lost focus and might need to be closed */
FOnMenuLostFocus OnMenuLostFocus;
};
/** Global handler used to handle key presses on popup menus */
FReply OnMenuKeyDown(const FKey Key)
{
if (Key == EKeys::Escape)
{
FSlateApplication::Get().DismissAllMenus();
return FReply::Handled();
}
return FReply::Unhandled();
}
} // anon namespace
TSharedRef<SWindow> FMenuStack::PushMenu( const TSharedRef<SWindow>& ParentWindow, const TSharedRef<SWidget>& InContent, const FVector2D& SummonLocation, const FPopupTransitionEffect& TransitionEffect, const bool bFocusImmediately, const bool bShouldAutoSize, const FVector2D& WindowSize, const FVector2D& SummonLocationSize)
{
// Deprecated method that is no longer called by its deprecated counterpart in FSlateApplication so it will only create a dummy message warning the caller not to use this method
// Backwards compatibility of the API is taken care of by FSlateApplication.
ensureMsgf(false, TEXT("PushMenu() returning an SWindow is deprecated. Use Push() that returns an IMenu instead."));
FSlateRect Anchor(SummonLocation, SummonLocation + SummonLocationSize);
FVector2D ExpectedSize(300, 200);
FVector2D AdjustedSummonLocation = FSlateApplication::Get().CalculatePopupWindowPosition(Anchor, ExpectedSize, Orient_Vertical);
TSharedRef<SWindow> NewMenuWindow =
SNew(SWindow)
.Type(EWindowType::Menu)
.IsPopupWindow(true)
.SizingRule(bShouldAutoSize ? ESizingRule::Autosized : ESizingRule::UserSized)
.ScreenPosition(AdjustedSummonLocation)
.AutoCenter(EAutoCenter::None)
.ClientSize(ExpectedSize)
.FocusWhenFirstShown(true)
.ActivationPolicy(EWindowActivationPolicy::Always)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.VAlign(VAlign_Center)
.HAlign(HAlign_Center)
[
SNew(STextBlock)
.Text(LOCTEXT("PushMenuDeprecatedWarning", "WARNING: PushMenu() returning an SWindow is deprecated. Use Push() that returns an IMenu instead."))
]
];
FSlateApplication::Get().AddWindowAsNativeChild(NewMenuWindow, ParentWindow, true);
return NewMenuWindow;
}
TSharedRef<IMenu> FMenuStack::Push(const FWidgetPath& InOwnerPath, const TSharedRef<SWidget>& InContent, const FVector2D& SummonLocation, const FPopupTransitionEffect& TransitionEffect, const bool bFocusImmediately, const FVector2D& SummonLocationSize, TOptional<EPopupMethod> InMethod, const bool bIsCollapsedByParent, const bool bEnablePerPixelTransparency)
{
// We want to ensure that when the window is restored to restore the current keyboard focus
InOwnerPath.GetWindow()->SetWidgetToFocusOnActivate(FSlateApplication::Get().GetKeyboardFocusedWidget());
FSlateRect Anchor(SummonLocation, SummonLocation + SummonLocationSize);
TSharedPtr<IMenu> ParentMenu;
if (HasMenus())
{
// Find a menu in the stack in InOwnerPath to determine the level to insert this
ParentMenu = FindMenuInWidgetPath(InOwnerPath);
check(HostWindow.IsValid());
}
if (!ParentMenu.IsValid())
{
// pushing a new root menu (leave ParentMenu invalid)
// The active method is determined when a new root menu is pushed
ActiveMethod = InMethod.IsSet() ? FPopupMethodReply::UseMethod(InMethod.GetValue()) : QueryPopupMethod(InOwnerPath);
// The host window is determined when a new root menu is pushed
// This must be set prior to PushInternal below, as it will be referenced if the menu being created is a new root menu.
SetHostPath(InOwnerPath);
}
TGuardValue<bool> Guard(bHostWindowGuard, true);
return PushInternal(ParentMenu, InContent, Anchor, TransitionEffect, bFocusImmediately, ActiveMethod.GetShouldThrottle(), bIsCollapsedByParent, bEnablePerPixelTransparency);
}
TSharedRef<IMenu> FMenuStack::Push(const TSharedPtr<IMenu>& InParentMenu, const TSharedRef<SWidget>& InContent, const FVector2D& SummonLocation, const FPopupTransitionEffect& TransitionEffect, const bool bFocusImmediately, const FVector2D& SummonLocationSize, const bool bIsCollapsedByParent, const bool bEnablePerPixelTransparency)
{
check(Stack.Contains(InParentMenu));
check(HostWindow.IsValid());
FSlateRect Anchor(SummonLocation, SummonLocation + SummonLocationSize);
return PushInternal(InParentMenu, InContent, Anchor, TransitionEffect, bFocusImmediately, EShouldThrottle::Yes, bIsCollapsedByParent, bEnablePerPixelTransparency);
}
TSharedRef<IMenu> FMenuStack::PushHosted(const FWidgetPath& InOwnerPath, const TSharedRef<IMenuHost>& InMenuHost, const TSharedRef<SWidget>& InContent, TSharedPtr<SWidget>& OutWrappedContent, const FPopupTransitionEffect& TransitionEffect, EShouldThrottle ShouldThrottle, const bool bIsCollapsedByParent)
{
TSharedPtr<IMenu> ParentMenu;
if (HasMenus())
{
// Find a menu in the stack in InOwnerPath to determine the level to insert this
ParentMenu = FindMenuInWidgetPath(InOwnerPath);
check(HostWindow.IsValid());
}
if (!ParentMenu.IsValid())
{
// pushing a new root menu (leave ParentMenu invalid)
// The active method is determined when a new root menu is pushed and hosted menus are always UseCurrentWindow
ActiveMethod = FPopupMethodReply::UseMethod(EPopupMethod::UseCurrentWindow);
// The host window is determined when a new root menu is pushed
SetHostPath(InOwnerPath);
}
return PushHosted(ParentMenu, InMenuHost, InContent, OutWrappedContent, TransitionEffect, ShouldThrottle);
}
TSharedRef<IMenu> FMenuStack::PushHosted(const TSharedPtr<IMenu>& InParentMenu, const TSharedRef<IMenuHost>& InMenuHost, const TSharedRef<SWidget>& InContent, TSharedPtr<SWidget>& OutWrappedContent, const FPopupTransitionEffect& TransitionEffect, EShouldThrottle ShouldThrottle, const bool bIsCollapsedByParent)
{
check(HostWindow.IsValid());
// Create a FMenuInHostWidget
TSharedRef<SWidget> WrappedContent = WrapContent(InContent);
TSharedRef<FMenuInHostWidget> OutMenu = MakeShareable(new FMenuInHostWidget(InMenuHost, WrappedContent, bIsCollapsedByParent));
PendingNewMenu = OutMenu;
// Set the returned content - this must be drawn by the hosting widget until the menu gets dismissed and calls IMenuHost::OnMenuDismissed on its host
OutWrappedContent = WrappedContent;
// Register to get a callback when it's dismissed - to fixup stack
OutMenu->GetOnMenuDismissed().AddRaw(this, &FMenuStack::OnMenuDestroyed);
PostPush(InParentMenu, OutMenu, ShouldThrottle);
PendingNewMenu.Reset();
return OutMenu;
}
TSharedRef<IMenu> FMenuStack::PushInternal(const TSharedPtr<IMenu>& InParentMenu, const TSharedRef<SWidget>& InContent, FSlateRect Anchor, const FPopupTransitionEffect& TransitionEffect, const bool bFocusImmediately, EShouldThrottle ShouldThrottle, const bool bIsCollapsedByParent, const bool bEnablePerPixelTransparency)
{
FPrePushArgs PrePushArgs;
PrePushArgs.Content = InContent;
PrePushArgs.Anchor = Anchor;
PrePushArgs.TransitionEffect = TransitionEffect;
PrePushArgs.bFocusImmediately = bFocusImmediately;
PrePushArgs.bIsCollapsedByParent = bIsCollapsedByParent;
// Pre-push stage
// Determines correct layout
// Wraps content
// Other common setup steps needed by all (non-hosted) menus
const FPrePushResults PrePushResults = PrePush(PrePushArgs);
// Menu object creation stage
TSharedRef<FMenuBase> OutMenu = ActiveMethod.GetPopupMethod() == EPopupMethod::CreateNewWindow
? PushNewWindow(InParentMenu, PrePushResults, bEnablePerPixelTransparency)
: PushPopup(InParentMenu, PrePushResults);
// Post-push stage
// Updates the stack and content map member variables
PostPush(InParentMenu, OutMenu, ShouldThrottle);
PendingNewMenu.Reset();
return OutMenu;
}
FMenuStack::FPrePushResults FMenuStack::PrePush(const FPrePushArgs& InArgs)
{
FPrePushResults OutResults;
OutResults.bIsCollapsedByParent = InArgs.bIsCollapsedByParent;
OutResults.bFocusImmediately = InArgs.bFocusImmediately;
if (InArgs.bFocusImmediately)
{
OutResults.WidgetToFocus = InArgs.Content;
}
// Only enable window position/size transitions if we're running at a decent frame rate
OutResults.bAllowAnimations = FSlateApplication::Get().AreMenuAnimationsEnabled() && FSlateApplication::Get().IsRunningAtTargetFrameRate();
// Calc the max height available on screen for the menu
float MaxHeight;
if (ActiveMethod.GetPopupMethod() == EPopupMethod::CreateNewWindow)
{
FSlateRect WorkArea = FSlateApplication::Get().GetWorkArea(InArgs.Anchor);
MaxHeight = FMenuStackDefs::MaxMenuScreenHeightFraction * WorkArea.GetSize().Y;
}
else
{
MaxHeight = FMenuStackDefs::MaxMenuScreenHeightFraction * HostWindow->GetClientSizeInScreen().Y;
}
bool bAnchorSetsMinWidth = InArgs.TransitionEffect.SlideDirection == FPopupTransitionEffect::ComboButton;
// Wrap menu content in a box needed for various sizing and tracking purposes
FOptionalSize OptionalMinWidth = bAnchorSetsMinWidth ? InArgs.Anchor.GetSize().X : FOptionalSize();
FOptionalSize OptionalMinHeight = MaxHeight;
// Wrap content in an SPopup before the rest of the wrapping process - should make menus appear on top using deferred presentation
TSharedRef<SWidget> TempContent = SNew(SPopup)[InArgs.Content.ToSharedRef()];
OutResults.WrappedContent = WrapContent(TempContent, OptionalMinWidth, OptionalMinHeight);
OutResults.WrappedContent->SlatePrepass(FSlateApplication::Get().GetApplicationScale() * HostWindow->GetNativeWindow()->GetDPIScaleFactor());
// @todo slate: Doesn't take into account potential window border size
OutResults.ExpectedSize = OutResults.WrappedContent->GetDesiredSize();
EOrientation Orientation = (InArgs.TransitionEffect.SlideDirection == FPopupTransitionEffect::SubMenu) ? Orient_Horizontal : Orient_Vertical;
// Calculate the correct position for the menu based on the popup method
if (ActiveMethod.GetPopupMethod() == EPopupMethod::CreateNewWindow)
{
// Places the menu's window in the work area
OutResults.AnimStartLocation = OutResults.AnimFinalLocation = FSlateApplication::Get().CalculatePopupWindowPosition(InArgs.Anchor, OutResults.ExpectedSize, Orientation);
}
else
{
// Places the menu's content in the host window
const FVector2D ProposedPlacement(
Orientation == Orient_Horizontal ? InArgs.Anchor.Right : InArgs.Anchor.Left,
Orientation == Orient_Horizontal ? InArgs.Anchor.Top : InArgs.Anchor.Bottom);
OutResults.AnimStartLocation = OutResults.AnimFinalLocation =
ComputePopupFitInRect(InArgs.Anchor, FSlateRect(ProposedPlacement, ProposedPlacement + OutResults.ExpectedSize), Orientation, HostWindow->GetClientRectInScreen());
}
// Start the pop-up menu at an offset location, then animate it to its target location over time
// @todo: Anims aren't supported or attempted - this is legacy code left in in case we reinstate menu anims
if (OutResults.bAllowAnimations)
{
const bool bSummonRight = OutResults.AnimFinalLocation.X >= OutResults.AnimStartLocation.X;
const bool bSummonBelow = OutResults.AnimFinalLocation.Y >= OutResults.AnimStartLocation.Y;
const int32 SummonDirectionX = bSummonRight ? 1 : -1;
const int32 SummonDirectionY = bSummonBelow ? 1 : -1;
switch (InArgs.TransitionEffect.SlideDirection)
{
case FPopupTransitionEffect::None:
// No sliding
break;
case FPopupTransitionEffect::ComboButton:
OutResults.AnimStartLocation.Y = FMath::Max(OutResults.AnimStartLocation.Y + 30.0f * SummonDirectionY, 0.0f);
break;
case FPopupTransitionEffect::TopMenu:
OutResults.AnimStartLocation.Y = FMath::Max(OutResults.AnimStartLocation.Y + 60.0f * SummonDirectionY, 0.0f);
break;
case FPopupTransitionEffect::SubMenu:
OutResults.AnimStartLocation.X += 60.0f * SummonDirectionX;
break;
case FPopupTransitionEffect::TypeInPopup:
OutResults.AnimStartLocation.Y = FMath::Max(OutResults.AnimStartLocation.Y + 30.0f * SummonDirectionY, 0.0f);
break;
case FPopupTransitionEffect::ContextMenu:
OutResults.AnimStartLocation.X += 30.0f * SummonDirectionX;
OutResults.AnimStartLocation.Y += 50.0f * SummonDirectionY;
break;
}
}
// Release the mouse so that context can be properly restored upon closing menus. See CL 1411833 before changing this.
if (InArgs.bFocusImmediately)
{
FSlateApplication::Get().ReleaseMouseCapture();
}
return OutResults;
}
TSharedRef<FMenuBase> FMenuStack::PushNewWindow(TSharedPtr<IMenu> InParentMenu, const FPrePushResults& InPrePushResults, const bool bEnablePerPixelTransparency)
{
check(ActiveMethod.GetPopupMethod() == EPopupMethod::CreateNewWindow);
// Start pop-up windows out transparent, then fade them in over time
#if ALPHA_BLENDED_WINDOWS
const EWindowTransparency Transparency(bEnablePerPixelTransparency ? EWindowTransparency::PerPixel : InPrePushResults.bAllowAnimations ? EWindowTransparency::PerWindow : EWindowTransparency::None);
#else
const EWindowTransparency Transparency(InPrePushResults.bAllowAnimations ? EWindowTransparency::PerWindow : EWindowTransparency::None);
#endif
const float InitialWindowOpacity = InPrePushResults.bAllowAnimations ? 0.0f : 1.0f;
const float TargetWindowOpacity = 1.0f;
// Create a new window for the menu
TSharedRef<SWindow> NewMenuWindow = SNew(SWindow)
.Type(EWindowType::Menu)
.IsPopupWindow(true)
.SizingRule(ESizingRule::Autosized)
.ScreenPosition(InPrePushResults.AnimStartLocation)
.AutoCenter(EAutoCenter::None)
.ClientSize(InPrePushResults.ExpectedSize)
.InitialOpacity(InitialWindowOpacity)
.SupportsTransparency(Transparency)
.FocusWhenFirstShown(InPrePushResults.bFocusImmediately)
.ActivationPolicy(InPrePushResults.bFocusImmediately ? EWindowActivationPolicy::Always : EWindowActivationPolicy::Never)
[
InPrePushResults.WrappedContent.ToSharedRef()
];
PendingNewWindow = NewMenuWindow;
if (InPrePushResults.bFocusImmediately && InPrePushResults.WidgetToFocus.IsValid())
{
// Focus the unwrapped content rather than just the window
NewMenuWindow->SetWidgetToFocusOnActivate(InPrePushResults.WidgetToFocus);
}
TSharedRef<FMenuInWindow> Menu = MakeShareable(new FMenuInWindow(NewMenuWindow, InPrePushResults.WrappedContent.ToSharedRef(), InPrePushResults.bIsCollapsedByParent));
PendingNewMenu = Menu;
TSharedPtr<SWindow> ParentWindow;
if (InParentMenu.IsValid())
{
ParentWindow = InParentMenu->GetParentWindow();
}
else
{
ParentWindow = HostWindow;
}
FSlateApplication::Get().AddWindowAsNativeChild(NewMenuWindow, ParentWindow.ToSharedRef(), true);
// Kick off the intro animation!
// @todo: Anims aren't supported or attempted - this is legacy code left in in case we reinstate menu anims
if (InPrePushResults.bAllowAnimations)
{
FCurveSequence Sequence;
Sequence.AddCurve(0, FMenuStackDefs::AnimationDuration, ECurveEaseFunction::CubicOut);
NewMenuWindow->MorphToPosition(Sequence, TargetWindowOpacity, InPrePushResults.AnimFinalLocation);
}
PendingNewWindow.Reset();
return Menu;
}
TSharedRef<FMenuBase> FMenuStack::PushPopup(TSharedPtr<IMenu> InParentMenu, const FPrePushResults& InPrePushResults)
{
check(ActiveMethod.GetPopupMethod() == EPopupMethod::UseCurrentWindow);
// Create a FMenuInPopup
check(InPrePushResults.WrappedContent.IsValid());
TSharedRef<FMenuInPopup> Menu = MakeShareable(new FMenuInPopup(InPrePushResults.WrappedContent.ToSharedRef(), InPrePushResults.bIsCollapsedByParent));
PendingNewMenu = Menu;
// Register to get callback when it's dismissed - to fixup stack
Menu->GetOnMenuDismissed().AddRaw(this, &FMenuStack::OnMenuDestroyed);
// Add it to a slot on the menus panel widget
HostWindowPopupPanel->PushMenu(Menu, InPrePushResults.AnimFinalLocation);
if (InPrePushResults.bFocusImmediately && InPrePushResults.WidgetToFocus.IsValid())
{
FSlateApplication::Get().SetKeyboardFocus(InPrePushResults.WidgetToFocus, EFocusCause::SetDirectly);
}
return Menu;
}
void FMenuStack::PostPush(TSharedPtr<IMenu> InParentMenu, TSharedRef<FMenuBase> InMenu, EShouldThrottle ShouldThrottle )
{
// Determine at which index we insert this new menu in the stack
int32 InsertIndex = 0;
if (InParentMenu.IsValid())
{
int32 ParentIndex = Stack.IndexOfByKey(InParentMenu);
check(ParentIndex != INDEX_NONE);
InsertIndex = ParentIndex + 1;
}
// Insert before dismissing others to stop the stack accidentally emptying briefly mid-push and reseting some state
Stack.Insert(InMenu, InsertIndex);
CachedContentMap.Add(InMenu->GetContent(), InMenu);
// Dismiss menus after the insert point
if (Stack.Num() > InsertIndex + 1)
{
DismissFrom(Stack[InsertIndex + 1]);
// tidy the stack data now (it will happen via callbacks from the dismissed menus but that might be delayed)
for (int32 StackIndex = Stack.Num() - 1; StackIndex > InsertIndex; --StackIndex)
{
CachedContentMap.Remove(Stack[StackIndex]->GetContent());
Stack.RemoveAt(StackIndex);
}
}
// When a new menu is pushed, if we are not already in responsive mode for Slate UI, enter it now
// to ensure the menu is responsive in low FPS situations
if (!ThrottleHandle.IsValid() && ShouldThrottle == EShouldThrottle::Yes)
{
ThrottleHandle = FSlateThrottleManager::Get().EnterResponsiveMode();
}
}
FPopupMethodReply FMenuStack::QueryPopupMethod(const FWidgetPath& PathToQuery)
{
for (int32 WidgetIndex = PathToQuery.Widgets.Num() - 1; WidgetIndex >= 0; --WidgetIndex)
{
FPopupMethodReply PopupMethod = PathToQuery.Widgets[WidgetIndex].Widget->OnQueryPopupMethod();
if (PopupMethod.IsEventHandled())
{
return PopupMethod;
}
}
return FPopupMethodReply::UseMethod(EPopupMethod::CreateNewWindow);
}
void FMenuStack::Dismiss(int32 FirstStackIndexToRemove)
{
// deprecated
DismissInternal(FirstStackIndexToRemove);
}
void FMenuStack::DismissFrom(const TSharedPtr<IMenu>& InFromMenu)
{
int32 Index = Stack.IndexOfByKey(InFromMenu);
if (Index != INDEX_NONE)
{
DismissInternal(Index);
}
}
void FMenuStack::DismissAll()
{
const int32 TopLevel = 0;
DismissInternal(TopLevel);
}
void FMenuStack::DismissInternal(int32 FirstStackIndexToRemove)
{
// Dismiss the stack in reverse order so we destroy children before parents (causes focusing issues if done the other way around)
for ( int32 StackIndex = Stack.Num() - 1; StackIndex >= FirstStackIndexToRemove; --StackIndex )
{
if ( Stack.IsValidIndex(StackIndex) )
{
Stack[StackIndex]->Dismiss();
}
}
}
void FMenuStack::SetHostPath(const FWidgetPath& InOwnerPath)
{
if (bHostWindowGuard)
{
return;
}
if ( HostPopupLayer.IsValid() )
{
if ( !InOwnerPath.ContainsWidget(HostPopupLayer->GetHost()) )
{
HostPopupLayer->Remove();
HostPopupLayer.Reset();
HostWindowPopupPanel.Reset();
}
}
HostWindow = InOwnerPath.IsValid() ? InOwnerPath.GetWindow() : TSharedPtr<SWindow>();
if ( HostWindow.IsValid() && !HostWindowPopupPanel.IsValid() )
{
TSharedRef<SMenuPanel> NewHostWindowPopupPanel = SNew(SMenuPanel);
for ( int i = InOwnerPath.Widgets.Num() - 1; i >= 0; i-- )
{
const TSharedRef<SWidget>& HostWidget = InOwnerPath.Widgets[i].Widget;
HostPopupLayer = HostWidget->OnVisualizePopup(NewHostWindowPopupPanel);
if ( HostPopupLayer.IsValid() )
{
HostWindowPopupPanel = NewHostWindowPopupPanel;
break;
}
}
}
}
void FMenuStack::OnMenuDestroyed(TSharedRef<IMenu> InMenu)
{
int32 Index = Stack.IndexOfByKey(InMenu);
if (Index != INDEX_NONE)
{
// Dismiss menus below this one
for (int32 StackIndex = Stack.Num() - 1; StackIndex > Index; --StackIndex)
{
Stack[StackIndex]->Dismiss(); // this will cause OnMenuDestroyed() to re-enter
}
// Clean up the stack and content map arrays
for (int32 StackIndex = Stack.Num() - 1; StackIndex >= Index; --StackIndex)
{
CachedContentMap.Remove(Stack[StackIndex]->GetContent());
Stack.RemoveAt(StackIndex);
}
// Leave responsive mode once the last menu closes
if (Stack.Num() == 0)
{
if (ThrottleHandle.IsValid())
{
FSlateThrottleManager::Get().LeaveResponsiveMode(ThrottleHandle);
}
SetHostPath(FWidgetPath());
}
}
}
void FMenuStack::OnMenuContentLostFocus(const FWidgetPath& InFocussedPath)
{
// In UseCurrentWindow mode we must look for focus moving from menus
// Window activation messages will make menus collapse when in CreateNewWindow mode
// However, we cannot rely on window activation messages because they might not be generated on Mac.
// So, always do this focus/collapse code, even in CreateNewWindow mode.
if (HasMenus() && !PendingNewMenu.IsValid())
{
// If focus is switching determine which of our menus it is in, if any
TSharedPtr<IMenu> FocussedMenu = FindMenuInWidgetPath(InFocussedPath);
if (FocussedMenu.IsValid())
{
// dismiss menus below FocussedMenu
int32 FocussedIndex = Stack.IndexOfByKey(FocussedMenu);
check(FocussedIndex != INDEX_NONE);
for (int32 DismissIndex = FocussedIndex + 1; DismissIndex < Stack.Num(); DismissIndex++)
{
if (Stack[DismissIndex]->IsCollapsedByParent())
{
DismissFrom(Stack[DismissIndex]);
break;
}
}
}
else
{
// Focus has moved outside all menus - collapse the stack
DismissAll();
}
}
}
TSharedRef<SWidget> FMenuStack::WrapContent(TSharedRef<SWidget> InContent, FOptionalSize OptionalMinWidth, FOptionalSize OptionalMinHeight)
{
// Wrap menu content in a box that limits its maximum height
// and in a SMenuContentWrapper that handles key presses and focus changes.
return SNew(SMenuContentWrapper)
.OnKeyDown_Static(&OnMenuKeyDown)
.OnMenuLostFocus_Raw(this, &FMenuStack::OnMenuContentLostFocus)
.OptionalMinMenuWidth(OptionalMinWidth)
.OptionalMinMenuHeight(OptionalMinHeight)
.MenuContent()
[
InContent
];
}
TSharedPtr<IMenu> FMenuStack::FindMenuInWidgetPath(const FWidgetPath& PathToQuery) const
{
for (int32 PathIndex = PathToQuery.Widgets.Num() - 1; PathIndex >= 0; --PathIndex)
{
TSharedPtr<const SWidget> Widget = PathToQuery.Widgets[PathIndex].Widget;
const TSharedPtr<FMenuBase>* FoundMenu = CachedContentMap.Find(Widget);
if (FoundMenu != nullptr)
{
return *FoundMenu;
}
}
return TSharedPtr<IMenu>();
}
void FMenuStack::RemoveWindow( TSharedRef<SWindow> WindowToRemove )
{
// deprecated
OnWindowDestroyed(WindowToRemove);
}
void FMenuStack::OnWindowDestroyed(TSharedRef<SWindow> InWindow)
{
if (HostWindow == InWindow)
{
// If the host window is destroyed, collapse the whole stack and reset all state
Stack.Empty();
CachedContentMap.Empty();
SetHostPath(FWidgetPath());
}
else
{
// A window was requested to be destroyed, so make sure it's not in the menu stack to avoid it
// becoming a parent to a freshly-created window!
TSharedPtr<IMenu> Menu = FindMenuFromWindow(InWindow);
if (Menu.IsValid())
{
OnMenuDestroyed(Menu.ToSharedRef());
}
}
}
void FMenuStack::OnWindowActivated( TSharedRef<SWindow> ActivatedWindow )
{
if (ActivatedWindow != PendingNewWindow && HasMenus())
{
TWeakPtr<IMenu> ActivatedMenu = FindMenuFromWindow(ActivatedWindow);
if (ActivatedMenu.IsValid())
{
// Dismiss menus below ActivatedMenu
int32 ActivatedIndex = Stack.IndexOfByKey(ActivatedMenu);
check(ActivatedIndex != INDEX_NONE);
for (int32 DismissIndex = ActivatedIndex + 1; DismissIndex < Stack.Num(); DismissIndex++)
{
if (Stack[DismissIndex]->IsCollapsedByParent())
{
DismissFrom(Stack[DismissIndex]);
break;
}
}
}
else
{
// Activated a window that isn't a menu - collapse the stack
DismissAll();
}
}
}
int32 FMenuStack::FindLocationInStack( TSharedPtr<SWindow> WindowToFind ) const
{
// deprecated
if (WindowToFind.IsValid())
{
TWeakPtr<IMenu> Menu = FindMenuFromWindow(WindowToFind.ToSharedRef());
return Stack.IndexOfByKey(Menu);
}
// The window was not found
return INDEX_NONE;
}
TSharedPtr<IMenu> FMenuStack::FindMenuFromWindow(TSharedRef<SWindow> InWindow) const
{
const TSharedPtr<FMenuBase>* FoundMenu = Stack.FindByPredicate([InWindow](TSharedPtr<FMenuBase> Menu) { return Menu->GetOwnedWindow() == InWindow; });
if (FoundMenu != nullptr)
{
return *FoundMenu;
}
return TSharedPtr<IMenu>();
}
FSlateRect FMenuStack::GetToolTipForceFieldRect(TSharedRef<IMenu> InMenu, const FWidgetPath& PathContainingMenu) const
{
FSlateRect ForceFieldRect(0, 0, 0, 0);
int32 StackLevel = Stack.IndexOfByKey(InMenu);
if (StackLevel != INDEX_NONE)
{
for (int32 StackLevelIndex = StackLevel + 1; StackLevelIndex < Stack.Num(); ++StackLevelIndex)
{
TSharedPtr<SWidget> MenuContent = Stack[StackLevelIndex]->GetContent();
if (MenuContent.IsValid())
{
FWidgetPath WidgetPath = PathContainingMenu.GetPathDownTo(MenuContent.ToSharedRef());
if (!WidgetPath.IsValid())
{
FSlateApplication::Get().GeneratePathToWidgetChecked(MenuContent.ToSharedRef(), WidgetPath);
}
if (WidgetPath.IsValid())
{
const FGeometry& ContentGeometry = WidgetPath.Widgets.Last().Geometry;
ForceFieldRect = ForceFieldRect.Expand(ContentGeometry.GetClippingRect());
}
}
}
}
return ForceFieldRect;
}
bool FMenuStack::HasMenus() const
{
return Stack.Num() > 0;
}
bool FMenuStack::HasOpenSubMenus( const TSharedRef<SWindow>& Window ) const
{
// deprecated
TWeakPtr<IMenu> Menu = FindMenuFromWindow(Window);
if (Menu.IsValid())
{
return Menu != Stack.Last();
}
return false;
}
bool FMenuStack::HasOpenSubMenus(TSharedPtr<IMenu> InMenu) const
{
int32 StackIndex = Stack.IndexOfByKey(InMenu);
return StackIndex != INDEX_NONE && StackIndex < Stack.Num() - 1;
}
int32 FMenuStack::GetNumStackLevels() const
{
return Stack.Num();
}
TSharedPtr<SWindow> FMenuStack::GetHostWindow() const
{
return HostWindow;
}
FMenuWindowList& FMenuStack::GetWindowsAtStackLevel( const int32 StackLevelIndex )
{
// deprecated
// can't support this so return an empty list
ensure(false);
static FMenuWindowList EmptyList;
return EmptyList;
}
#undef LOCTEXT_NAMESPACE