Files
UnrealEngine/Engine/Source/Runtime/Slate/Private/Widgets/Input/SMenuAnchor.cpp
2025-05-18 13:04:45 +08:00

724 lines
28 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Widgets/Input/SMenuAnchor.h"
#include "Layout/ArrangedChildren.h"
#include "Rendering/DrawElements.h"
#include "Widgets/SWindow.h"
#include "Layout/WidgetPath.h"
#include "Framework/Application/Menu.h"
#include "Framework/Application/MenuStack.h"
#include "Framework/Application/SlateApplication.h"
#include "Layout/LayoutUtils.h"
#if WITH_EDITOR
#include "Framework/MultiBox/MultiBox.h"
#include "Framework/MultiBox/ToolMenuBase.h"
#endif
static FVector2D GetMenuOffsetForPlacement(const FGeometry& AllottedGeometry, EMenuPlacement PlacementMode, FVector2D PopupSizeLocalSpace)
{
switch (PlacementMode)
{
case MenuPlacement_BelowAnchor:
return FVector2D(0.0f, AllottedGeometry.GetLocalSize().Y);
case MenuPlacement_CenteredBelowAnchor:
return FVector2D(-((PopupSizeLocalSpace.X / 2) - (AllottedGeometry.GetLocalSize().X / 2)), AllottedGeometry.GetLocalSize().Y);
case MenuPlacement_BelowRightAnchor:
return FVector2D( -( PopupSizeLocalSpace.X ) + ( AllottedGeometry.GetLocalSize().X ), AllottedGeometry.GetLocalSize().Y );
case MenuPlacement_ComboBox:
return FVector2D(0.0f, AllottedGeometry.GetLocalSize().Y);
case MenuPlacement_ComboBoxRight:
return FVector2D(AllottedGeometry.GetLocalSize().X - PopupSizeLocalSpace.X, AllottedGeometry.GetLocalSize().Y);
case MenuPlacement_MenuRight:
return FVector2D(AllottedGeometry.GetLocalSize().X, 0.0f);
case MenuPlacement_AboveAnchor:
return FVector2D(0.0f, -PopupSizeLocalSpace.Y);
case MenuPlacement_CenteredAboveAnchor:
return FVector2D(-((PopupSizeLocalSpace.X / 2) - (AllottedGeometry.GetLocalSize().X / 2)), -PopupSizeLocalSpace.Y);
case MenuPlacement_AboveRightAnchor:
return FVector2D( -( PopupSizeLocalSpace.X ) - ( AllottedGeometry.GetLocalSize().X ), -PopupSizeLocalSpace.Y );
case MenuPlacement_MenuLeft:
return FVector2D(-PopupSizeLocalSpace.X, 0.0f);
case MenuPlacement_Center:
return FVector2D( -( ( PopupSizeLocalSpace.X / 2 ) - ( AllottedGeometry.GetLocalSize().X / 2 ) ), -( ( PopupSizeLocalSpace.Y / 2 ) - ( AllottedGeometry.GetLocalSize().Y / 2 ) ) );
case MenuPlacement_RightLeftCenter:
return FVector2D( AllottedGeometry.GetLocalSize().X, - ( ( PopupSizeLocalSpace.Y / 2 ) - ( AllottedGeometry.GetLocalSize().Y / 2 ) ) );
case MenuPlacement_MatchBottomLeft:
return FVector2D(0.0f, (AllottedGeometry.GetLocalSize().Y - PopupSizeLocalSpace.Y));
default:
ensureMsgf( false, TEXT("Unhandled placement mode: %d"), PlacementMode );
return FVector2D::ZeroVector;
}
}
SMenuAnchor::FPopupPlacement::FPopupPlacement(const FGeometry& PlacementGeometry, const FVector2D& PopupDesiredSize, EMenuPlacement PlacementMode)
{
// Compute the popup size, offset, and anchor rect in local space
const bool bIsComboBoxPopup = (PlacementMode == MenuPlacement_ComboBox || PlacementMode == MenuPlacement_ComboBoxRight);
LocalPopupSize = bIsComboBoxPopup ? FVector2D(FMath::Max(PlacementGeometry.Size.X, PopupDesiredSize.X), PopupDesiredSize.Y) : PopupDesiredSize;
LocalPopupOffset = GetMenuOffsetForPlacement(PlacementGeometry, PlacementMode, LocalPopupSize);
AnchorLocalSpace = FSlateRect::FromPointAndExtent(FVector2D::ZeroVector, PlacementGeometry.GetLocalSize());
Orientation = (PlacementMode == MenuPlacement_MenuRight || PlacementMode == MenuPlacement_MenuLeft) ? Orient_Horizontal : Orient_Vertical;
}
/*static*/ TArray<TWeakPtr<IMenu>> SMenuAnchor::OpenApplicationMenus;
/**
* Construct this widget
*
* @param InArgs The declaration data for this widget
*/
void SMenuAnchor::Construct( const FArguments& InArgs )
{
Children.AddSlot(MoveTemp(FBasicLayoutWidgetSlot::FSlotArguments(MakeUnique<FBasicLayoutWidgetSlot>())
.Padding(InArgs._Padding)
[
InArgs._Content.Widget
]));
Children.AddSlot(FBasicLayoutWidgetSlot::FSlotArguments(MakeUnique<FBasicLayoutWidgetSlot>()));
MenuContent = InArgs._MenuContent;
WrappedContent = InArgs._MenuContent;
OnGetMenuContent = InArgs._OnGetMenuContent;
OnMenuOpenChanged = InArgs._OnMenuOpenChanged;
Placement = InArgs._Placement;
bFitInWindow = InArgs._FitInWindow;
Method = InArgs._Method;
bShouldDeferPaintingAfterWindowContent = InArgs._ShouldDeferPaintingAfterWindowContent;
bUseApplicationMenuStack = InArgs._UseApplicationMenuStack;
bShowMenuBackground = InArgs._ShowMenuBackground;
bIsCollapsedByParent = InArgs._IsCollapsedByParent;
bApplyWidgetStyleToMenu = InArgs._ApplyWidgetStyleToMenu;
}
FGeometry SMenuAnchor::ComputeNewWindowMenuPlacement(const FGeometry& AllottedGeometry, const FVector2D& PopupDesiredSize, EMenuPlacement PlacementMode) const
{
// Compute the popup size, offset, and anchor rect in local space
const FPopupPlacement PopupPlacement(AllottedGeometry, PopupDesiredSize, PlacementMode);
// already handled
const bool bAutoAdjustForDPIScale = false;
// ask the application to compute the proper desktop offset for the anchor. This requires the offsets to be in desktop space.
const FVector2D NewPositionDesktopSpace = FSlateApplication::Get().CalculatePopupWindowPosition(
TransformRect(AllottedGeometry.GetAccumulatedLayoutTransform(), PopupPlacement.AnchorLocalSpace),
TransformVector(AllottedGeometry.GetAccumulatedLayoutTransform(), PopupPlacement.LocalPopupSize),
bAutoAdjustForDPIScale,
TransformPoint(AllottedGeometry.GetAccumulatedLayoutTransform(), PopupPlacement.LocalPopupOffset),
PopupPlacement.Orientation);
// transform the desktop offset into local space and use that as the layout transform for the child content.
return AllottedGeometry.MakeChild(
PopupPlacement.LocalPopupSize,
FSlateLayoutTransform(TransformPoint(Inverse(AllottedGeometry.GetAccumulatedLayoutTransform()), NewPositionDesktopSpace)));
}
void SMenuAnchor::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime )
{
TSharedPtr<SWindow> PopupWindow = PopupWindowPtr.Pin();
if ( PopupWindow.IsValid() && IsOpenViaCreatedWindow() )
{
// Figure out where our attached pop-up window should be placed.
const FVector2D PopupContentDesiredSize = PopupWindow->GetContent()->GetDesiredSize();
FGeometry PopupGeometry = ComputeNewWindowMenuPlacement( AllottedGeometry, PopupContentDesiredSize, Placement.Get() );
const FVector2D NewPosition = PopupGeometry.LocalToAbsolute(FVector2D::ZeroVector);
// NOTE: In order to get the right size of the window, we need to take whatever the incoming scale of the menu anchor,
// then divide out the popup window's DPI scale. Finally, we need to divide that remainder to the draw size.
// The idea here is to divide out any "extra" scale that's not associated with the DPI scale, since ComputeNewWindowMenuPlacement
// makes a child transform for the new window, based on the geometry of it, which if the menu anchor is inside a zoom panel
// that would translate to a menu that had a smaller size window, if the scale was tiny, which we don't want - we only want
// the DPI scale if any, to be factored into the size.
// NOTE: We only do this for "New Window" popups. Because games use "Current Window", we can't do this same trick, as they have
// to be concerned with the viewport scale as well, which Slate knows nothing about. Perhaps DPI Scale of the viewports should be
// passed down in FGeometry, along with the window DPI Scale, as one extra value code can take into account if it needs to.
const FVector2D NewSize = PopupGeometry.GetDrawSize() / ( AllottedGeometry.GetAccumulatedLayoutTransform().GetScale() / PopupWindow->GetLocalToWindowTransform().GetScale() );
// When in a ComboBox, we want the submenu to be at minimum the size of the combo box, so we update the Popupwindow minimum size.
const EMenuPlacement PlacementMode = Placement.Get();
if (PlacementMode == MenuPlacement_ComboBox)
{
// Set the new size limits
FWindowSizeLimits SizeLimits = PopupWindow->GetSizeLimits();
SizeLimits.SetMinWidth(NewSize.X);
PopupWindow->SetSizeLimits(SizeLimits);
}
// We made a window for showing the popup.
// Update the window's position!
PopupWindow->ReshapeWindow(NewPosition, NewSize);
}
else if (PopupWindow.IsValid() && IsOpenAndReusingWindow())
{
// Ideally, do this in OnArrangeChildren(); currently not possible because OnArrangeChildren()
// can be called in DesktopSpace or WindowSpace, and we will not know which version of the Window
// geometry to use. Tick() is always in DesktopSpace, so cache the position here and just use
// it in OnArrangeChildren().
const FPopupPlacement LocalPlacement(AllottedGeometry, Children[1].GetWidget()->GetDesiredSize(), Placement.Get());
FVector2D FittedPlacement;
if (bFitInWindow)
{
const FSlateRect WindowRectLocalSpace = TransformRect(Inverse(AllottedGeometry.GetAccumulatedLayoutTransform()), PopupWindow->GetClientRectInScreen());
FittedPlacement = ComputePopupFitInRect(
LocalPlacement.AnchorLocalSpace,
FSlateRect(LocalPlacement.LocalPopupOffset, LocalPlacement.LocalPopupOffset + LocalPlacement.LocalPopupSize),
LocalPlacement.Orientation, WindowRectLocalSpace);
}
else
{
FittedPlacement = LocalPlacement.LocalPopupOffset;
}
LocalPopupPosition = FittedPlacement;
ScreenPopupPosition = AllottedGeometry.GetAccumulatedLayoutTransform().TransformPoint(LocalPopupPosition);
}
/** The tick is ending, so the window was not dismissed this tick. */
bDismissedThisTick = false;
}
bool SMenuAnchor::ComputeVolatility() const
{
return SPanel::ComputeVolatility() || IsOpen();
}
void SMenuAnchor::OnArrangeChildren( const FGeometry& AllottedGeometry, FArrangedChildren& ArrangedChildren ) const
{
ArrangeSingleChild(GSlateFlowDirection, AllottedGeometry, ArrangedChildren, Children[0], FVector2D::UnitVector);
const TSharedPtr<SWindow> PresentingWindow = PopupWindowPtr.Pin();
if (IsOpenAndReusingWindow() && PresentingWindow.IsValid())
{
const FPopupPlacement LocalPlacement(AllottedGeometry, Children[1].GetWidget()->GetDesiredSize(), Placement.Get());
ArrangedChildren.AddWidget(AllottedGeometry.MakeChild(Children[1].GetWidget(), LocalPlacement.LocalPopupSize, FSlateLayoutTransform(LocalPopupPosition)));
}
}
FVector2D SMenuAnchor::ComputeDesiredSize( float ) const
{
const FVector2D DesiredWidgetSize = Children[0].GetWidget()->GetDesiredSize();
// Menu anchors might be created with null content, in which case they must still get drawn in order to
// draw pop-up content, therefore it must lie and always request a desired size of at least 1,1, otherwise
// a panel may filter it from drawing thinking the it doesn't have anything to draw.
return FVector2D(FMath::Max(DesiredWidgetSize.X, 1.0f), FMath::Max(DesiredWidgetSize.Y, 1.0f));
}
FChildren* SMenuAnchor::GetChildren()
{
return &Children;
}
int32 SMenuAnchor::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const
{
FArrangedChildren ArrangedChildren( EVisibility::Visible );
ArrangeChildren( AllottedGeometry, ArrangedChildren );
// There may be zero elements in this array if our child collapsed/hidden
if ( ArrangedChildren.Num() > 0 )
{
const FArrangedWidget& FirstChild = ArrangedChildren[0];
// In the case where the user doesn't provide content to the menu anchor, the null widget
// wont appear in the visible set of arranged children, so only immediately paint the first child,
// if it's visible and matches the first slot content.
const bool bHasArrangedAnchorContent = FirstChild.Widget == Children[0].GetWidget();
if ( bHasArrangedAnchorContent )
{
LayerId = FirstChild.Widget->Paint(Args.WithNewParent(this), FirstChild.Geometry, MyCullingRect, OutDrawElements, LayerId + 1, InWidgetStyle, ShouldBeEnabled(bParentEnabled));
}
const bool bIsOpen = IsOpen();
if ( bIsOpen )
{
// In the case where the anchor content is present and visible, it's the 1 index child, in the case
// where the anchor content is invisible, it's the 0 index child.
FArrangedWidget* PopupChild = nullptr;
if ( bHasArrangedAnchorContent && ArrangedChildren.Num() > 1 )
{
PopupChild = &ArrangedChildren[1];
}
else if ( !bHasArrangedAnchorContent && ArrangedChildren.Num() == 1 )
{
PopupChild = &ArrangedChildren[0];
}
if ( PopupChild != nullptr )
{
if (bShouldDeferPaintingAfterWindowContent)
{
OutDrawElements.QueueDeferredPainting(
FSlateWindowElementList::FDeferredPaint(PopupChild->Widget, Args, PopupChild->Geometry, bApplyWidgetStyleToMenu ? InWidgetStyle : FWidgetStyle(), bParentEnabled));
}
else
{
const TSharedPtr<SWindow> PresentingWindow = PopupWindowPtr.Pin();
if (PresentingWindow.IsValid())
{
PopupChild->Widget->Paint(Args.WithNewParent(this), PopupChild->Geometry, PresentingWindow->GetClippingRectangleInWindow(), OutDrawElements, LayerId + 1, bApplyWidgetStyleToMenu ? InWidgetStyle : FWidgetStyle(), ShouldBeEnabled(bParentEnabled));
}
}
}
}
}
return LayerId;
}
bool SMenuAnchor::IsOpenAndReusingWindow() const
{
return MethodInUse.IsSet() && MethodInUse.GetPopupMethod() == EPopupMethod::UseCurrentWindow;
}
bool SMenuAnchor::IsOpenViaCreatedWindow() const
{
return MethodInUse.IsSet() && MethodInUse.GetPopupMethod() == EPopupMethod::CreateNewWindow;
}
void SMenuAnchor::SetContent(TSharedRef<SWidget> InContent)
{
Children[0].SetPadding(0.f);
Children[0].AttachWidget(InContent);
}
void SMenuAnchor::SetMenuContent(TSharedRef<SWidget> InMenuContent)
{
MenuContent = InMenuContent;
WrappedContent = InMenuContent; // wrapping, if any will happen when the menu is opened
}
FPopupMethodReply 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 SMenuAnchor::SetIsOpen( bool InIsOpen, const bool bFocusMenu, const int32 FocusUserIndex )
{
// Prevent redundant opens/closes
if ( IsOpen() != InIsOpen )
{
if ( InIsOpen )
{
if ( OnGetMenuContent.IsBound() )
{
SetMenuContent(OnGetMenuContent.Execute());
}
if ( MenuContent.IsValid() )
{
// OPEN POPUP
if ( OnMenuOpenChanged.IsBound() )
{
OnMenuOpenChanged.Execute(true);
}
// Figure out where the menu anchor is on the screen, so we can set the initial position of our pop-up window
// This can be called at any time so we use the push menu override that explicitly allows us to specify our parent
// NOTE: Careful, GeneratePathToWidget can be reentrant in that it can call visibility delegates and such
FWidgetPath MyWidgetPath;
FSlateApplication::Get().GeneratePathToWidgetUnchecked(AsShared(), MyWidgetPath);
if (MyWidgetPath.IsValid())
{
const FGeometry& MyGeometry = MyWidgetPath.Widgets.Last().Geometry;
const float LayoutScaleMultiplier = MyGeometry.GetAccumulatedLayoutTransform().GetScale();
SlatePrepass(LayoutScaleMultiplier);
// Figure out how big the content widget is so we can set the window's initial size properly
TSharedRef< SWidget > MenuContentRef = MenuContent.ToSharedRef();
MenuContentRef->SlatePrepass(LayoutScaleMultiplier);
// Combo-boxes never size down smaller than the widget that spawned them, but all
// other pop-up menus are currently auto-sized
const FVector2D DesiredContentSize = MenuContentRef->GetDesiredSize(); // @todo slate: This is ignoring any window border size!
const EMenuPlacement PlacementMode = Placement.Get();
const FVector2D NewPosition = FVector2D(MyGeometry.AbsolutePosition);
FVector2D NewWindowSize = DesiredContentSize;
FPopupTransitionEffect TransitionEffect( FPopupTransitionEffect::None );
if ( PlacementMode == MenuPlacement_ComboBox || PlacementMode == MenuPlacement_ComboBoxRight )
{
TransitionEffect = FPopupTransitionEffect( FPopupTransitionEffect::ComboButton );
NewWindowSize = FVector2D( FMath::Max( MyGeometry.Size.X, DesiredContentSize.X ), DesiredContentSize.Y );
}
else if ( PlacementMode == MenuPlacement_BelowAnchor )
{
TransitionEffect = FPopupTransitionEffect( FPopupTransitionEffect::TopMenu );
}
else if ( PlacementMode == MenuPlacement_MenuRight )
{
TransitionEffect = FPopupTransitionEffect( FPopupTransitionEffect::SubMenu );
NewWindowSize = MyGeometry.GetAbsoluteSize();
}
MethodInUse = Method.IsSet()
? FPopupMethodReply::UseMethod(Method.GetValue())
: QueryPopupMethod(MyWidgetPath);
// "Normal" menus are created and managed by the application's menu stack functions
if (bUseApplicationMenuStack)
{
if (MethodInUse.GetPopupMethod() == EPopupMethod::CreateNewWindow)
{
// Open the pop-up
TSharedPtr<IMenu> NewMenu = FSlateApplication::Get().PushMenu(AsShared(), MyWidgetPath, MenuContentRef, NewPosition, TransitionEffect, bFocusMenu, NewWindowSize, MethodInUse.GetPopupMethod(), bIsCollapsedByParent, FocusUserIndex);
if (ensure(NewMenu.IsValid()))
{
#if WITH_EDITOR
// Reporting more information for UE-81655
if (!NewMenu->GetOwnedWindow().IsValid())
{
UE_LOG(LogSlate, Error, TEXT("!NewMenu->GetOwnedWindow().IsValid(), WidgetPath:\n%s"), *MyWidgetPath.ToString());
static const FName SMultiBoxWidgetTypeName = "SMultiBoxWidget";
for (int32 WidgetIndex = MyWidgetPath.Widgets.Num() - 1; WidgetIndex >= 0; --WidgetIndex)
{
if (MyWidgetPath.Widgets[WidgetIndex].Widget->GetType() == SMultiBoxWidgetTypeName)
{
TSharedRef<SMultiBoxWidget> MultiBoxWidget = StaticCastSharedRef<SMultiBoxWidget>(MyWidgetPath.Widgets[WidgetIndex].Widget);
TSharedRef<const FMultiBox> MultiBox = MultiBoxWidget->GetMultiBox();
FString BlockText;
const TArray< TSharedRef< const FMultiBlock > >& Blocks = MultiBox->GetBlocks();
for (int32 BlockIndex = 0; BlockIndex < Blocks.Num(); ++BlockIndex)
{
const bool bIsFinalBlock = (BlockIndex == (Blocks.Num() - 1));
const TSharedRef< const FMultiBlock >& Block = Blocks[BlockIndex];
BlockText += FString::Printf(TEXT("%s (%d)%s"), *Block->GetExtensionHook().ToString(), (int32)Block->GetType(), bIsFinalBlock ? TEXT("") : TEXT(", "));
}
UE_LOG(LogSlate, Error, TEXT(" Blocks: %s"), *BlockText);
break;
}
}
UE_LOG(LogSlate, Error, TEXT(" MenuContentRef: %s"), *MenuContentRef->ToString());
}
#endif
if (NewMenu->GetOwnedWindow().IsValid())
{
PopupMenuPtr = NewMenu;
NewMenu->GetOnMenuDismissed().AddSP(this, &SMenuAnchor::OnMenuClosed);
PopupWindowPtr = NewMenu->GetOwnedWindow();
}
else
{
UE_LOG(LogSlate, Error, TEXT(" Menu '%s' could not open '%s'"), *ToString(), *MenuContentRef->ToString());
if (TSharedPtr<IMenu> Pinned = PopupMenuPtr.Pin())
{
Pinned->Dismiss();
}
ResetPopupMenuContent();
}
}
}
else
{
// We are re-using the current window instead of creating a new one.
// The popup will be presented as a child of this widget.
ensure(MethodInUse.GetPopupMethod() == EPopupMethod::UseCurrentWindow);
// We get the deepest window so that it works correctly inside a widget component
// though we may need to come up with a more complex setup if we ever need
// parents to be in a virtual window, but not the popup.
PopupWindowPtr = MyWidgetPath.GetDeepestWindow();
if (bFocusMenu)
{
FSlateApplication::Get().ReleaseAllPointerCapture(FocusUserIndex);
}
TSharedRef<SMenuAnchor> SharedThis = StaticCastSharedRef<SMenuAnchor>(AsShared());
TSharedPtr<IMenu> NewMenu = FSlateApplication::Get().PushHostedMenu(
SharedThis, MyWidgetPath, SharedThis, MenuContentRef, WrappedContent, TransitionEffect, MethodInUse.GetShouldThrottle(), bIsCollapsedByParent);
PopupMenuPtr = NewMenu;
check(NewMenu.IsValid());
//check(NewMenu->GetParentWindow().ToSharedRef() == PopupWindow);
check(WrappedContent.IsValid());
Children[1]
[
WrappedContent.ToSharedRef()
];
if (bFocusMenu)
{
FSlateApplication::Get().SetUserFocus(FocusUserIndex, MenuContentRef, EFocusCause::SetDirectly);
}
}
}
else // !bUseApplicationMenuStack
{
// Anchor's menu doesn't participate in the application's menu stack.
// Lifetime is managed by this anchor
if (MethodInUse.GetPopupMethod() == EPopupMethod::CreateNewWindow)
{
// Start pop-up windows out transparent, then fade them in over time
const EWindowTransparency Transparency(EWindowTransparency::PerWindow);
FSlateRect Anchor(NewPosition, NewPosition + MyGeometry.GetLocalSize());
EOrientation Orientation = (TransitionEffect.SlideDirection == FPopupTransitionEffect::SubMenu) ? Orient_Horizontal : Orient_Vertical;
// @todo slate: Assumes that popup is not Scaled up or down from application scale.
MenuContentRef->SlatePrepass(FSlateApplication::Get().GetApplicationScale());
// @todo slate: Doesn't take into account potential window border size
FVector2D ExpectedSize = MenuContentRef->GetDesiredSize();
// already handled
const bool bAutoAdjustForDPIScale = false;
const FVector2D ScreenPosition = FSlateApplication::Get().CalculatePopupWindowPosition(Anchor, ExpectedSize, bAutoAdjustForDPIScale, FVector2D::ZeroVector, Orientation);
// Release the mouse so that context can be properly restored upon closing menus. See CL 1411833 before changing this.
if (bFocusMenu)
{
FSlateApplication::Get().ReleaseAllPointerCapture(FocusUserIndex);
}
// Create a new window for the menu
TSharedRef<SWindow> NewMenuWindow = SNew(SWindow)
.Type(EWindowType::Menu)
.IsPopupWindow(true)
.SizingRule(ESizingRule::Autosized)
.ScreenPosition(ScreenPosition)
.AutoCenter(EAutoCenter::None)
.ClientSize(ExpectedSize)
.InitialOpacity(1.0f)
.SupportsTransparency(Transparency)
.FocusWhenFirstShown(bFocusMenu)
.ActivationPolicy(bFocusMenu ? EWindowActivationPolicy::Always : EWindowActivationPolicy::Never)
[
MenuContentRef
];
if (bFocusMenu)
{
// Focus the unwrapped content rather than just the window
NewMenuWindow->SetWidgetToFocusOnActivate(MenuContentRef);
}
TSharedPtr<IMenu> NewMenu = MakeShareable(new FMenuInWindow(NewMenuWindow, MenuContentRef, bIsCollapsedByParent));
FSlateApplication::Get().AddWindowAsNativeChild(NewMenuWindow, MyWidgetPath.GetWindow(), true);
PopupMenuPtr = OwnedMenuPtr = NewMenu;
check(NewMenu.IsValid());
NewMenu->GetOnMenuDismissed().AddSP(this, &SMenuAnchor::OnMenuClosed);
PopupWindowPtr = NewMenuWindow;
}
else
{
// We are re-using the current window instead of creating a new one.
// The popup will be presented as a child of this widget.
ensure(MethodInUse.GetPopupMethod() == EPopupMethod::UseCurrentWindow);
PopupWindowPtr = MyWidgetPath.GetWindow();
if (bFocusMenu)
{
FSlateApplication::Get().ReleaseAllPointerCapture(FocusUserIndex);
}
TSharedRef<SMenuAnchor> SharedThis = StaticCastSharedRef<SMenuAnchor>(AsShared());
TSharedPtr<IMenu> NewMenu = MakeShareable(new FMenuInHostWidget(SharedThis, MenuContentRef, bIsCollapsedByParent));
PopupMenuPtr = OwnedMenuPtr = NewMenu;
check(NewMenu.IsValid());
//check(NewMenu->GetParentWindow().ToSharedRef() == PopupWindow);
Children[1]
[
MenuContentRef
];
if (bFocusMenu)
{
FSlateApplication::Get().SetUserFocus(FocusUserIndex, MenuContentRef, EFocusCause::SetDirectly);
}
OpenApplicationMenus.Add(NewMenu);
}
}
}
}
Invalidate(EInvalidateWidget::ChildOrder | EInvalidateWidget::Volatility);
}
else
{
// CLOSE POPUP
if (PopupMenuPtr.IsValid())
{
PopupMenuPtr.Pin()->Dismiss();
}
ResetPopupMenuContent();
}
}
}
void SMenuAnchor::OnMenuClosed(TSharedRef<IMenu> InMenu)
{
bDismissedThisTick = true;
ResetPopupMenuContent();
if (OnMenuOpenChanged.IsBound())
{
OnMenuOpenChanged.Execute(false);
}
if ( OnGetMenuContent.IsBound() )
{
SetMenuContent(SNullWidget::NullWidget);
}
}
void SMenuAnchor::ResetPopupMenuContent()
{
MethodInUse = FPopupMethodReply::Unhandled();
PopupMenuPtr.Reset();
OwnedMenuPtr.Reset();
PopupWindowPtr.Reset();
// Always clear out the menu content children slot to prevent prepass and other hierarchy queries from considering the
// hidden menu content as content they should be concerned with.
Children[1]
[
SNullWidget::NullWidget
];
Invalidate(EInvalidateWidget::ChildOrder | EInvalidateWidget::Volatility);
}
bool SMenuAnchor::IsOpen() const
{
return MethodInUse.IsSet() && PopupMenuPtr.IsValid();
}
bool SMenuAnchor::ShouldOpenDueToClick() const
{
return !IsOpen() && !bDismissedThisTick;
}
FVector2D SMenuAnchor::GetMenuPosition() const
{
FVector2D Pos(0,0);
if (IsOpenViaCreatedWindow() && PopupWindowPtr.IsValid())
{
Pos = PopupWindowPtr.Pin()->GetPositionInScreen();
}
else if (IsOpenAndReusingWindow() && PopupMenuPtr.IsValid())
{
Pos = ScreenPopupPosition;
}
return Pos;
}
void SMenuAnchor::SetMenuPlacement(TAttribute<EMenuPlacement> InMenuPlacement)
{
if (!Placement.IsSet() || !Placement.IdenticalTo(InMenuPlacement))
{
Placement = InMenuPlacement;
Invalidate(EInvalidateWidget::Layout);
}
}
void SMenuAnchor::SetFitInWindow(bool bFit)
{
if (bFitInWindow != bFit)
{
bFitInWindow = bFit;
Invalidate(EInvalidateWidget::Layout);
}
}
bool SMenuAnchor::HasOpenSubMenus() const
{
bool Result = false;
if (PopupMenuPtr.IsValid())
{
Result = FSlateApplication::Get().HasOpenSubMenus(PopupMenuPtr.Pin());
}
return Result;
}
TSharedPtr<SWindow> SMenuAnchor::GetMenuWindow() const
{
return IsOpen() ? PopupWindowPtr.Pin() : TSharedPtr<SWindow>();
}
void SMenuAnchor::OnMenuDismissed()
{
if (PopupMenuPtr.IsValid())
{
OnMenuClosed(PopupMenuPtr.Pin().ToSharedRef());
}
}
bool SMenuAnchor::UsingApplicationMenuStack() const
{
return bUseApplicationMenuStack;
}
/*static*/ void SMenuAnchor::DismissAllApplicationMenus()
{
for (int32 i = 0; i < OpenApplicationMenus.Num(); ++i)
{
TSharedPtr<IMenu> Iter = OpenApplicationMenus[i].IsValid() ? OpenApplicationMenus[i].Pin() : nullptr;
if (Iter.IsValid() && Iter->UsingApplicationMenuStack())
{
Iter->Dismiss();
OpenApplicationMenus.RemoveAtSwap(i--);
}
}
}
SMenuAnchor::SMenuAnchor()
: MenuContent( SNullWidget::NullWidget )
, WrappedContent(SNullWidget::NullWidget)
, bDismissedThisTick( false )
, Method()
, MethodInUse()
, LocalPopupPosition( FVector2D::ZeroVector )
, Children(this)
{
}
SMenuAnchor::~SMenuAnchor()
{
if (PopupMenuPtr.IsValid())
{
PopupMenuPtr.Pin()->Dismiss();
// If the menu hasn't been dismissed, then dismiss it now
if (!bDismissedThisTick)
{
bDismissedThisTick = true;
if (OnMenuOpenChanged.IsBound())
{
OnMenuOpenChanged.Execute(false);
}
}
}
// We no longer have a popup open, so reset all the tracking state associated.
PopupMenuPtr.Reset();
OwnedMenuPtr.Reset();
PopupWindowPtr.Reset();
MethodInUse = FPopupMethodReply::Unhandled();
}