// 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> 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()) .Padding(InArgs._Padding) [ InArgs._Content.Widget ])); Children.AddSlot(FBasicLayoutWidgetSlot::FSlotArguments(MakeUnique())); 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 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 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 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 InContent) { Children[0].SetPadding(0.f); Children[0].AttachWidget(InContent); } void SMenuAnchor::SetMenuContent(TSharedRef 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 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 MultiBoxWidget = StaticCastSharedRef(MyWidgetPath.Widgets[WidgetIndex].Widget); TSharedRef 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 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 SharedThis = StaticCastSharedRef(AsShared()); TSharedPtr 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 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 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 SharedThis = StaticCastSharedRef(AsShared()); TSharedPtr 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 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 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 SMenuAnchor::GetMenuWindow() const { return IsOpen() ? PopupWindowPtr.Pin() : TSharedPtr(); } 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 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(); }