Files
2025-05-18 13:04:45 +08:00

389 lines
18 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Input/PopupMethodReply.h"
#include "Framework/Application/IMenu.h"
#include "Layout/SlateRect.h"
#include "Widgets/SWidget.h"
#include "Types/SlateStructs.h"
#include "Widgets/SWindow.h"
#include "Application/ThrottleManager.h"
#include "Delegates/DelegateCombinations.h"
class FWidgetPath;
class SMenuPanel;
class FMenuBase;
typedef TArray<TSharedPtr<SWindow>> FMenuWindowList; // deprecated
#if UE_BUILD_DEBUG
// Debugging is harder with the inline allocator
typedef TArray<TSharedPtr<IMenu>> FMenuList;
#else
typedef TArray<TSharedPtr<IMenu>, TInlineAllocator<4>> FMenuList;
#endif
/** Describes a simple animation for popup window introductions */
struct FPopupTransitionEffect
{
/** Direction to slide in from */
enum ESlideDirection
{
/** No sliding */
None,
/** Sliding direction for a combo button */
ComboButton,
/** Sliding direction for a top-level pull-down menu or combo box */
TopMenu,
/** Sliding direction for a sub-menu */
SubMenu,
/** Sliding direction for a popup that lets the user type in data */
TypeInPopup,
/** Sliding direction preferred for context menu popups */
ContextMenu,
} SlideDirection;
/** Constructor */
FPopupTransitionEffect( const ESlideDirection InitSlideDirection )
: SlideDirection(InitSlideDirection)
{ }
};
DECLARE_MULTICAST_DELEGATE_OneParam(FOnMenuDestroyed, const TSharedRef<IMenu>& InMenu);
#if WITH_SLATE_DEBUGGING
extern FAutoConsoleVariable CVarSlateDismissMenuStacksOnFocusLost;
#endif
/**
* Represents a stack of open menus. The last item in the stack is the top most menu
* Menus are described as IMenus. Implementations of IMenu can control how the menus are created and presented (e.g. in their own window)
*/
class FMenuStack
{
public:
/** Constructor */
FMenuStack()
: bHostWindowGuard(false)
{}
/**
* Pushes a new menu onto the stack. The widget path will be searched for existing menus and the new menu will be parented appropriately.
* Menus are always auto-sized. Use fixed-size content if a fixed size is required.
*
* @param InOwnerPath The widget path for the parent widget of this menu.
* @param InContent The menu's content
* @param SummonLocation Location in screen-space where the menu should appear
* @param TransitionEffect Animation to use when the popup appears
* @param bFocusImmediately Should the popup steal focus when shown?
* @param SummonLocationSize An optional size around the summon location which describes an area in which the menu may not appear
* @param InMethod An optional popup method that will override the default method for the widgets in InOwnerPath
* @param bIsCollapsedByParent Is this menu collapsed when a parent menu receives focus/activation? If false, only focus/activation outside the entire stack will auto collapse it.
* @param bEnablePerPixelTransparency Does the menu's content require per pixel transparency?
* @param FocusUserIndex The index of the user that should focus the menu. INDEX_NONE for all users.
*/
TSharedRef<IMenu> Push(const FWidgetPath& InOwnerPath, const TSharedRef<SWidget>& InContent, const UE::Slate::FDeprecateVector2DParameter& SummonLocation, const FPopupTransitionEffect& TransitionEffect, const bool bFocusImmediately = true, const UE::Slate::FDeprecateVector2DParameter& SummonLocationSize = FVector2f::ZeroVector, TOptional<EPopupMethod> InMethod = TOptional<EPopupMethod>(), const bool bIsCollapsedByParent = true, const bool bEnablePerPixelTransparency = false, const int32 FocusUserIndex = INDEX_NONE);
/**
* Pushes a new child menu onto the stack.
* Menus are always auto-sized. Use fixed-size content if a fixed size is required.
*
* @param InParentMenu The parent menu for this menu
* @param InContent The menu's content
* @param SummonLocation Location in screen-space where the menu should appear
* @param TransitionEffect Animation to use when the popup appears
* @param bFocusImmediately Should the popup steal focus when shown?
* @param SummonLocationSize An optional size around the summon location which describes an area in which the menu may not appear
* @param bIsCollapsedByParent Is this menu collapsed when a parent menu receives focus/activation? If false, only focus/activation outside the entire stack will auto collapse it.
* @param bEnablePerPixelTransparency Does the menu's content require per pixel transparency?
* @param FocusUserIndex The index of the user that should focus the menu. INDEX_NONE for all users.
*/
TSharedRef<IMenu> Push(const TSharedPtr<IMenu>& InParentMenu, const TSharedRef<SWidget>& InContent, const UE::Slate::FDeprecateVector2DParameter& SummonLocation, const FPopupTransitionEffect& TransitionEffect, const bool bFocusImmediately = true, const UE::Slate::FDeprecateVector2DParameter& SummonLocationSize = FVector2f::ZeroVector, const bool bIsCollapsedByParent = true, const bool bEnablePerPixelTransparency = false, const int32 FocusUserIndex = INDEX_NONE);
/**
* Pushes a new menu onto the stack that is drawn by an external host widget.
* The widget path will be searched for existing menus and the new menu will be parented appropriately.
* Menus are always auto-sized. Use fixed-size content if a fixed size is required.
*
* @param InOwnerPath The widget path for the parent widget of this menu
* @param InMenuHost The host widget that draws the menu's content
* @param InContent The menu's content
* @param OutWrappedContent Returns the InContent wrapped with widgets needed by the menu stack system. This is what should be drawn by the host after this call.
* @param TransitionEffect Animation to use when the popup appears
* @param bIsCollapsedByParent Is this menu collapsed when a parent menu receives focus/activation? If false, only focus/activation outside the entire stack will auto collapse it.
*/
TSharedRef<IMenu> PushHosted(const FWidgetPath& InOwnerPath, const TSharedRef<IMenuHost>& InMenuHost, const TSharedRef<SWidget>& InContent, TSharedPtr<SWidget>& OutWrappedContent, const FPopupTransitionEffect& TransitionEffect, EShouldThrottle ShouldThrottle, const bool bIsCollapsedByParent = true);
/**
* Pushes a new child menu onto the stack that is drawn by an external host widget.
* Menus are always auto-sized. Use fixed-size content if a fixed size is required.
*
* @param InParentMenu The parent menu for this menu
* @param InMenuHost The host widget that draws the menu's content
* @param InContent The menu's content
* @param OutWrappedContent Returns the InContent wrapped with widgets needed by the menu stack system. This is what should be drawn by the host after this call.
* @param TransitionEffect Animation to use when the popup appears
* @param bIsCollapsedByParent Is this menu collapsed when a parent menu receives focus/activation? If false, only focus/activation outside the entire stack will auto collapse it.
*/
TSharedRef<IMenu> PushHosted(const TSharedPtr<IMenu>& InParentMenu, const TSharedRef<IMenuHost>& InMenuHost, const TSharedRef<SWidget>& InContent, TSharedPtr<SWidget>& OutWrappedContent, const FPopupTransitionEffect& TransitionEffect, EShouldThrottle ShouldThrottle, const bool bIsCollapsedByParent = true);
/**
* Dismisses the menu stack including InFromMenu and all its child menus
* Dismisses in reverse order (children first)
*
* @param InFromMenu The menu to remove along with its children
*/
void DismissFrom(const TSharedPtr<IMenu>& InFromMenu);
/**
* Dismisses the entire menu stack
*/
void DismissAll();
/**
* Called by the application when any window is destroyed.
*
* @param InWindow The window that was destroyed
*/
void OnWindowDestroyed(TSharedRef<SWindow> InWindow);
/**
* Notifies the stack that a new window was activated. The menu stack will be dismissed if the activated window is not a menu in the stack
*
* @param ActivatedWindow The window that was just activated
*/
void OnWindowActivated( TSharedRef<SWindow> ActivatedWindow );
/**
* Finds a menu in the stack that owns InWindow.
* Although this method isn't deprecated, it is intended for private use inside FMenuStack
* and its use as a public method should be avoided. Menus should be identified in client code as IMenu interfaces.
*
* @param InWindow The window to look for
* @return The menu in the stack that owns InWindow, or an invalid ptr if not found
*/
TSharedPtr<IMenu> FindMenuFromWindow(TSharedRef<SWindow> InWindow) const;
/**
* Searches from bottom to top of the widget path for a menu in the stack.
* If the widget at the end of PathToQuery is inside a menu, there will be a menu content wrapper widget in the path.
*
* @param PathToQuery The widget path to search for a menu
* @return The menu in the stack that contains the widget at the end of PathToQuery, or an invalid ptr if not found
*/
TSharedPtr<IMenu> FindMenuInWidgetPath(const FWidgetPath& PathToQuery) const;
/**
* Called by the application when showing tooltips. It prevents tooltips from drawing over menu items. If the resulting bool is false, the resulting OutSlateRect will be [0,0,0,0] and should not be used.
*
* @param InMenu A menu in the stack, used to generate the force field rect
* @param InPathContainingMenu A widget path containing InMenu. This could be generated but the application has the path so it helps performance to pass it in here.
* @param OutSlateRect The output rectangle enclosing the menu's children (created from the clipping rects in the geometry in PathContainingMenu)
* @return A bool indicating whether a solution was found. If false, the resulting OutSlateRect will be [0,0,0,0] and should not be used.
*/
bool GetToolTipForceFieldRect(const TSharedRef<IMenu>& InMenu, const FWidgetPath& InPathContainingMenu, FSlateRect& OutSlateRect) const;
/**
* @return Returns the window that is the parent of everything in the stack, if any. If the stack is empty, returns an invalid ptr.
*/
TSharedPtr<SWindow> GetHostWindow() const;
/**
* @return Returns the widget that is the parent widget that initially created the menu in the stack
*/
TSharedPtr<SWidget> GetHostWidget() const;
/**
* @return True if the stack has one or more menus in it, false if it is empty.
*/
bool HasMenus() const;
/**
* @return Returns whether the menu has child menus. If the menu isn't in the stack, returns false.
*/
bool HasOpenSubMenus(TSharedPtr<IMenu> InMenu) const;
/** Delegate called when a menu is dismissed and destroyed */
FOnMenuDestroyed& OnMenuDestroyedEvent() { return MenuDestroyedEvent; }
private:
/**
* Queries the widgets in the path for a menu popup method.
*
* @param PathToQuery The path to query
*
* @return The popup method chosen by a widget in the path. Defaults to EPopupMethod::CreateNewWindow if no widgets in the path have a preference.
*/
FPopupMethodReply QueryPopupMethod(const FWidgetPath& PathToQuery);
/**
* Called by public Dismiss methods. Dismisses all menus in the stack from FirstStackIndexToRemove and below.
*
* @param FirstStackIndexToRemove Index into the stack from which menus are dismissed.
*/
void DismissInternal(int32 FirstStackIndexToRemove);
/**
* Handles changes to the owner path, potentially locating and establishing the new host. Makes sure overlay slots are added/removed as appropriate.
*/
void SetHostPath(const FWidgetPath& InOwnerPath);
/**
* Callback method called by all menus when they are dismissed. Handles changes to the stack state when a menu is destroyed.
*
* @param InMenu The menu that was destroyed.
*/
void OnMenuDestroyed(TSharedRef<IMenu> InMenu);
/**
* Callback method called by all menus when they lose focus. Handles dismissing menus that have lost focus.
*
* @param InFocussedPath The new focus path.
*/
void OnMenuContentLostFocus(const FWidgetPath& InFocussedPath);
/**
* Called by all menus during creation. Handles wrapping content in SMenuContentWrapper that acts as a marker in widget paths and in a box that sets minimum size.
*
* @param InContent The unwrapped content.
* @param OptionalMinWidth Optional minimum width for the wrapped content.
* @param OptionalMinHeight Optional minimum height for the wrapped content.
* @param bShouldShowBackground Whether the menu wrapper should put a background behind the content.
*/
TSharedRef<SWidget> WrapContent(TSharedRef<SWidget> InContent, FOptionalSize OptionalMinWidth = FOptionalSize(), FOptionalSize OptionalMinHeight = FOptionalSize(), bool bShouldShowBackground = true);
/** Contains all the options passed to the pre-push stage of the menu creation process */
struct FPrePushArgs
{
FPrePushArgs() : TransitionEffect(FPopupTransitionEffect::None) {}
TSharedPtr<SWidget> Content;
FSlateRect Anchor;
FPopupTransitionEffect TransitionEffect;
int32 FocusUserIndex = INDEX_NONE;
bool bFocusImmediately;
bool bIsCollapsedByParent;
};
/** Contains all the options returned from the pre-push stage of the menu creation process */
struct FPrePushResults
{
TSharedPtr<SWidget> WrappedContent;
TSharedPtr<SWidget> WidgetToFocus;
FVector2f AnimStartLocation;
FVector2f AnimFinalLocation;
bool bAnchorSetsMinWidth;
FVector2f ExpectedSize;
bool bAllowAnimations;
bool bFocusImmediately;
bool bIsCollapsedByParent;
};
/**
* This is the pre-push stage of menu creation.
* It is responsible for wrapping content and calculating menu positioning. This stage is not used by "hosted" menus.
*
* @param InArgs Pre-push options.
*
* @return Pre-push results.
*/
FPrePushResults PrePush(const FPrePushArgs& InArgs);
/**
* This is the common code used during menu creation. This stage is not used by "hosted" menus.
* It handles pre-push, actual menu creation and post-push stack updates.
*
* @param InParentMenu The parent menu for this menu
* @param InContent The menu's content
* @param Anchor The anchor location for the menu (rect around the parent widget that is summoning the menu)
* @param TransitionEffect Animation to use when the popup appears
* @param bFocusImmediately Should the popup steal focus when shown?
* @param bIsCollapsedByParent Is this menu collapsed when a parent menu receives focus/activation? If false, only focus/activation outside the entire stack will auto collapse it.
* @param FocusUserIndex The index of the user that should focus the menu. INDEX_NONE for all users.
*
* @return The newly created menu.
*/
TSharedRef<IMenu> 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, const int32 FocusUserIndex = INDEX_NONE);
/**
* This is the actual menu object creation method for FMenuInWindow menus.
* It creates a new SWindow and new FMenuInWindow that uses it.
*
* @param InParentMenu The parent menu for this menu
* @param InPrePushResults The results of the pre-push stage
* @param FocusUserIndex The index of the user that should focus the window. INDEX_NONE for all users.
*
* @return The newly created FMenuInWindow menu.
*/
TSharedRef<FMenuBase> PushNewWindow(TSharedPtr<IMenu> InParentMenu, const FPrePushResults& InPrePushResults, const bool bEnablePerPixelTransparency, const int32 FocusUserIndex = INDEX_NONE);
/**
* This is the actual menu object creation method for FMenuInPopup menus.
* It creates a new FMenuInWindow and adds it into the overlay layer on the host window.
*
* @param InParentMenu The parent menu for this menu
* @param InPrePushResults The results of the pre-push stage
* @param FocusUserIndex The index of the user that should focus the popup. INDEX_NONE for all users.
*
* @return The newly created FMenuInPopup menu.
*/
TSharedRef<FMenuBase> PushPopup(TSharedPtr<IMenu> InParentMenu, const FPrePushResults& InPrePushResults, const int32 FocusUserIndex = INDEX_NONE);
/**
* This is the post-push stage of menu creation.
* It is responsible for updating the stack with the new menu and removing any that it replaces.
*
* @param InParentMenu The parent menu for this menu
* @param InMenu A newly created menu
* @param ShouldThrottle Should pushing this menu enable throttling of the engine for a more responsive UI
* @param bInInsertAfterDismiss Avoid unwanted delete due to dismissing another menu causing all menus in stack to be dismissed
*/
void PostPush(TSharedPtr<IMenu> InParentMenu, TSharedRef<FMenuBase> InMenu, EShouldThrottle ShouldThrottle, bool bInInsertAfterDismiss=false);
/** The popup method currently used by the whole stack. It can only use one at a time */
FPopupMethodReply ActiveMethod;
/** The parent window of the root menu in the stack. NOT the actual menu window if it's a CreateNewWindow */
TSharedPtr<SWindow> HostWindow;
/** The parent widget of the root menu in the stack */
TWeakPtr<SWidget> HostWidget;
/** The parent window of the root menu in the stack. NOT the actual menu window if it's a CreateNewWindow */
TSharedPtr<SMenuPanel> HostWindowPopupPanel;
/** The popup layer that contains our HostWindowPopupPanel. */
TSharedPtr<FPopupLayer> HostPopupLayer;
/** The array of menus in the stack */
TArray<TSharedPtr<class FMenuBase>> Stack;
/** Maps top-level content widgets (should always be SMenuContentWrappers) to menus in the stack */
TMap<TSharedPtr<const SWidget>, TSharedPtr<class FMenuBase>> CachedContentMap;
/** Handle to a throttle request made to ensure the menu is responsive in low FPS situations */
FThrottleRequest ThrottleHandle;
/** Temporary ptr to a new window created during the menu creation process. Nulled before the Push() call returns. Stops activation of the new window collapsing the stack. */
TSharedPtr<SWindow> PendingNewWindow;
/** Temporary ptr to a new menu created during the menu creation process. Nulled before the Push() call returns. Stops it collapsing the stack when it gets focus. */
TSharedPtr<class FMenuBase> PendingNewMenu;
/** Guard to prevent the HostWindow and HostWindowPopupPanel being set reentrantly */
bool bHostWindowGuard;
/** Called when a menu is destroyed */
FOnMenuDestroyed MenuDestroyedEvent;
};