170 lines
5.2 KiB
C++
170 lines
5.2 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "Widgets/Input/SSubMenuHandler.h"
|
|
#include "Framework/MultiBox/MultiBox.h"
|
|
|
|
void SSubMenuHandler::Construct(const FArguments& InArgs, TWeakPtr<SMenuOwner> InMenuOwner)
|
|
{
|
|
MenuOwnerWidget = InMenuOwner;
|
|
|
|
TSharedPtr< SWidget > ChildSlotWidget;
|
|
|
|
if (InArgs._MenuAnchor.IsValid())
|
|
{
|
|
MenuAnchor = InArgs._MenuAnchor;
|
|
ChildSlotWidget = InArgs._Content.Widget;
|
|
}
|
|
else
|
|
{
|
|
// If no way for a sub-menu is provided, do not use a MenuAnchor
|
|
if (InArgs._OnGetMenuContent.IsBound() || InArgs._MenuContent.IsValid())
|
|
{
|
|
ChildSlotWidget = SAssignNew(MenuAnchor, SMenuAnchor)
|
|
.Placement(InArgs._Placement)
|
|
.OnGetMenuContent(InArgs._OnGetMenuContent)
|
|
.MenuContent(InArgs._MenuContent)
|
|
[
|
|
InArgs._Content.Widget
|
|
];
|
|
}
|
|
else
|
|
{
|
|
ChildSlotWidget = InArgs._Content.Widget;
|
|
}
|
|
|
|
}
|
|
|
|
this->ChildSlot
|
|
[
|
|
ChildSlotWidget.ToSharedRef()
|
|
];
|
|
}
|
|
|
|
void SSubMenuHandler::OnMouseEnter( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
|
|
{
|
|
TSharedPtr< SMenuOwner > PinnedMenuOwnerWidget( MenuOwnerWidget.Pin() );
|
|
check( PinnedMenuOwnerWidget.IsValid() );
|
|
|
|
// Never dismiss another entry's submenu while the cursor is potentially moving toward that menu. It's
|
|
// not fun to try to keep the mouse in the menu entry bounds while moving towards the actual menu!
|
|
TSharedPtr< const SMenuAnchor > OpenedMenuAnchor = PinnedMenuOwnerWidget->GetOpenMenu();
|
|
const bool bSubMenuAlreadyOpen = ( OpenedMenuAnchor.IsValid() && OpenedMenuAnchor->IsOpen() );
|
|
bool bMouseEnteredTowardSubMenu = false;
|
|
{
|
|
if( bSubMenuAlreadyOpen )
|
|
{
|
|
const FVector2D& SubMenuPosition = OpenedMenuAnchor->GetMenuPosition();
|
|
const bool bIsMenuTowardRight = MouseEvent.GetScreenSpacePosition().X < SubMenuPosition.X;
|
|
const bool bDidMouseEnterTowardRight = MouseEvent.GetCursorDelta().X >= 0.0f; // NOTE: Intentionally inclusive of zero here.
|
|
bMouseEnteredTowardSubMenu = ( bIsMenuTowardRight == bDidMouseEnterTowardRight );
|
|
}
|
|
}
|
|
|
|
if (MenuAnchor.IsValid())
|
|
{
|
|
check( PinnedMenuOwnerWidget.IsValid() );
|
|
|
|
// Do we have a different pull-down menu open?
|
|
TSharedPtr< SMenuAnchor > PinnedMenuAnchor( MenuAnchor.Pin() );
|
|
if( PinnedMenuOwnerWidget->GetOpenMenu() != PinnedMenuAnchor )
|
|
{
|
|
const bool bClobber = bSubMenuAlreadyOpen && bMouseEnteredTowardSubMenu;
|
|
RequestSubMenuToggle( true, bClobber );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Hovering over a menu item that is not a sub-menu, we need to close any sub-menus that are open
|
|
const bool bClobber = bSubMenuAlreadyOpen && bMouseEnteredTowardSubMenu;
|
|
RequestSubMenuToggle( false, bClobber );
|
|
}
|
|
}
|
|
|
|
void SSubMenuHandler::OnMouseLeave( const FPointerEvent& MouseEvent )
|
|
{
|
|
SCompoundWidget::OnMouseLeave( MouseEvent );
|
|
|
|
// Reset any pending sub-menus that may be opening when we stop hovering over it
|
|
CancelPendingSubMenu();
|
|
}
|
|
|
|
bool SSubMenuHandler::ShouldSubMenuAppearHovered() const
|
|
{
|
|
// The sub-menu entry should appear hovered if the sub-menu is open. Except in the case that the user is actually interacting with this menu.
|
|
// In that case we need to show what the user is selecting
|
|
return MenuAnchor.IsValid() && MenuAnchor.Pin()->IsOpen() && !MenuOwnerWidget.Pin()->IsHovered();
|
|
}
|
|
|
|
void SSubMenuHandler::RequestSubMenuToggle( bool bOpenMenu, const bool bClobber, const bool bImmediate )
|
|
{
|
|
if (MenuAnchor.IsValid())
|
|
{
|
|
if (bImmediate)
|
|
{
|
|
UpdateSubMenuState(0, 0, true);
|
|
}
|
|
else
|
|
{
|
|
// Reset the time before the menu opens
|
|
float TimeToSubMenuOpen = bClobber ? MultiBoxConstants::SubMenuClobberTime : MultiBoxConstants::SubMenuOpenTime;
|
|
if (!ActiveTimerHandle.IsValid())
|
|
{
|
|
ActiveTimerHandle = RegisterActiveTimer(TimeToSubMenuOpen, FWidgetActiveTimerDelegate::CreateSP(this, &SSubMenuHandler::UpdateSubMenuState, bOpenMenu));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SSubMenuHandler::CancelPendingSubMenu()
|
|
{
|
|
// Reset any pending sub-menu openings
|
|
//SubMenuRequestState = Idle;
|
|
|
|
auto PinnedActiveTimerHandle = ActiveTimerHandle.Pin();
|
|
if (PinnedActiveTimerHandle.IsValid())
|
|
{
|
|
UnRegisterActiveTimer(PinnedActiveTimerHandle.ToSharedRef());
|
|
}
|
|
}
|
|
|
|
bool SSubMenuHandler::IsSubMenuOpen() const
|
|
{
|
|
return MenuAnchor.IsValid() && MenuAnchor.Pin()->IsOpen();
|
|
}
|
|
|
|
EActiveTimerReturnType SSubMenuHandler::UpdateSubMenuState(double InCurrentTime, float InDeltaTime, bool bWantsOpen)
|
|
{
|
|
TSharedPtr< SMenuOwner > PinnedMenuOwnerWidget(MenuOwnerWidget.Pin());
|
|
check(PinnedMenuOwnerWidget.IsValid());
|
|
|
|
if (bWantsOpen)
|
|
{
|
|
// For menu bar entries, we also need to handle mouse enter/leave events, so we can show and hide
|
|
// the pull-down menu appropriately
|
|
check(MenuAnchor.IsValid());
|
|
|
|
// Close other open pull-down menus from this menu bar
|
|
// Do we have a different pull-down menu open?
|
|
TSharedPtr< SMenuAnchor > PinnedMenuAnchor(MenuAnchor.Pin());
|
|
if (PinnedMenuOwnerWidget->GetOpenMenu() != PinnedMenuAnchor)
|
|
{
|
|
PinnedMenuOwnerWidget->CloseSummonedMenus();
|
|
|
|
// Summon the new pull-down menu!
|
|
if (PinnedMenuAnchor.IsValid())
|
|
{
|
|
PinnedMenuAnchor->SetIsOpen(true);
|
|
}
|
|
|
|
// Also tell the MenuOwner about this open pull-down menu, so it can be closed later if we need to
|
|
PinnedMenuOwnerWidget->SetSummonedMenu(PinnedMenuAnchor.ToSharedRef());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PinnedMenuOwnerWidget->CloseSummonedMenus();
|
|
}
|
|
|
|
return EActiveTimerReturnType::Stop;
|
|
}
|