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

210 lines
6.7 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Widgets/Input/SComboButton.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Input/SButton.h"
#include "Framework/Application/SlateApplication.h"
void SComboButton::Construct( const FArguments& InArgs )
{
check(InArgs._ComboButtonStyle);
Style = InArgs._ComboButtonStyle;
// Work out which values we should use based on whether we were given an override, or should use the style's version
const FButtonStyle* const OurButtonStyle = InArgs._ButtonStyle ? InArgs._ButtonStyle : &InArgs._ComboButtonStyle->ButtonStyle;
MenuBorderBrush = &InArgs._ComboButtonStyle->MenuBorderBrush;
MenuBorderPadding = InArgs._ComboButtonStyle->MenuBorderPadding;
OnComboBoxOpened = InArgs._OnComboBoxOpened;
ContentWidgetPtr = InArgs._MenuContent.Widget;
bIsFocusable = InArgs._IsFocusable;
const bool bHasDownArrowShadow = !InArgs._ComboButtonStyle->ShadowOffset.IsZero();
SMenuAnchor::Construct( SMenuAnchor::FArguments()
.Placement(InArgs._MenuPlacement)
.Method(InArgs._Method)
.OnMenuOpenChanged(InArgs._OnMenuOpenChanged)
.OnGetMenuContent(InArgs._OnGetMenuContent)
.IsCollapsedByParent(InArgs._CollapseMenuOnParentFocus)
[
SAssignNew(ButtonPtr, SButton )
.ButtonStyle( OurButtonStyle )
.ClickMethod( EButtonClickMethod::MouseDown )
.OnClicked( this, &SComboButton::OnButtonClicked )
.ToolTipText( this, &SComboButton::GetFilteredToolTipText, InArgs._ToolTipText)
.ContentPadding( InArgs._ContentPadding.IsSet() ? InArgs._ContentPadding : InArgs._ComboButtonStyle->ContentPadding )
.ForegroundColor( InArgs._ForegroundColor )
.ButtonColorAndOpacity( InArgs._ButtonColorAndOpacity )
.IsFocusable( InArgs._IsFocusable )
// We set the button to not be accessible as there are issues interacting with the menus that pop up e.g in SComboBox
.AccessibleParams(FAccessibleWidgetData(EAccessibleBehavior::NotAccessible, EAccessibleBehavior::NotAccessible, false))
[
// Button and down arrow on the right
// +-------------------+---+
// | Button Content | v |
// +-------------------+---+
SAssignNew( HBox, SHorizontalBox )
+ SHorizontalBox::Slot()
.Expose( ButtonContentSlot )
.FillWidth( 1 )
.HAlign( InArgs._HAlign )
.VAlign( InArgs._VAlign )
[
InArgs._ButtonContent.Widget
]
+SHorizontalBox::Slot()
.AutoWidth()
.HAlign( HAlign_Right )
.VAlign(InArgs._HasDownArrow ? (EVerticalAlignment) InArgs._ComboButtonStyle->DownArrowAlign : VAlign_Center)
.Padding(InArgs._HasDownArrow ? InArgs._ComboButtonStyle->DownArrowPadding : FMargin(0))
[
SNew(SOverlay)
// drop shadow
+ SOverlay::Slot()
.VAlign(VAlign_Top)
.Padding(FMargin(InArgs._ComboButtonStyle->ShadowOffset.X, InArgs._ComboButtonStyle->ShadowOffset.Y, 0, 0))
[
SAssignNew(ShadowImage, SImage)
.Visibility( InArgs._HasDownArrow && bHasDownArrowShadow ? EVisibility::Visible : EVisibility::Collapsed )
.Image( &InArgs._ComboButtonStyle->DownArrowImage )
.ColorAndOpacity( InArgs._ComboButtonStyle->ShadowColorAndOpacity )
]
+ SOverlay::Slot()
.VAlign(VAlign_Top)
[
SAssignNew(ForegroundArrowImage,SImage)
.Visibility( InArgs._HasDownArrow ? EVisibility::Visible : EVisibility::Collapsed )
.Image( &InArgs._ComboButtonStyle->DownArrowImage )
// Inherit tinting from parent
.ColorAndOpacity( FSlateColor::UseForeground() )
]
]
]
]
);
// The menu that pops up when we press the button.
// We keep this content around, and then put it into a new window when we need to pop
// it up.
SetMenuContent( InArgs._MenuContent.Widget );
}
FText SComboButton::GetFilteredToolTipText(TAttribute<FText> ToolTipText) const
{
if (IsOpen())
{
return FText::GetEmpty();
}
return ToolTipText.Get();
}
FReply SComboButton::OnButtonClicked()
{
// Button was clicked; show the popup.
// Do nothing if clicking on the button also dismissed the menu, because we will end up doing the same thing twice.
// Don't explicitly focus the menu, here, we're going to do it in the button reply so that it's properly focused
// to the correct user.
SetIsOpen( ShouldOpenDueToClick(), false );
// If the menu is open, execute the related delegate.
if( IsOpen() && OnComboBoxOpened.IsBound() )
{
OnComboBoxOpened.Execute();
}
// Focusing any newly-created widgets must occur after they have been added to the UI root.
FReply ButtonClickedReply = FReply::Handled();
// Don't try to focus the menu if the menu is closing
if (!IsOpen())
{
return ButtonClickedReply;
}
TSharedPtr<SWidget> WidgetToFocus = WidgetToFocusPtr.Pin();
if (bIsFocusable)
{
if (!WidgetToFocus.IsValid())
{
// no explicitly focused widget, try to focus the content that is a child of the border
if (WrappedContent.IsValid() && WrappedContent->GetChildren()->Num() == 1)
{
WidgetToFocus = WrappedContent->GetChildren()->GetChildAt(0);
}
}
if (!WidgetToFocus.IsValid())
{
// no explicitly focused widget, try to focus the content
WidgetToFocus = MenuContent;
}
if (!WidgetToFocus.IsValid())
{
// no content, so try to focus the original widget set on construction
WidgetToFocus = ContentWidgetPtr.Pin();
}
}
if (WidgetToFocus.IsValid())
{
ButtonClickedReply.SetUserFocus(WidgetToFocus.ToSharedRef(), EFocusCause::SetDirectly);
}
return ButtonClickedReply;
}
FReply SComboButton::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent)
{
FReply Reply = FReply::Unhandled();
if (FSlateApplication::Get().GetNavigationActionFromKey(InKeyEvent) == EUINavigationAction::Accept)
{
// Handle menu open with controller.
Reply = OnButtonClicked();
}
return Reply;
}
void SComboButton::SetMenuContent(TSharedRef<SWidget> InContent)
{
WrappedContent = MenuContent =
SNew(SBorder)
.BorderImage(MenuBorderBrush)
.Padding(MenuBorderPadding)
[
InContent
];
}
void SComboButton::SetOnGetMenuContent(FOnGetContent InOnGetMenuContent)
{
OnGetMenuContent = InOnGetMenuContent;
}
void SComboButton::SetButtonContentPadding(FMargin InPadding)
{
check(ButtonPtr);
ButtonPtr->SetContentPadding(InPadding);
}
void SComboButton::SetHasDownArrow(bool InHasArrowDown)
{
const bool bHasDownArrowShadow = !Style->ShadowOffset.IsZero();
check(HBox && HBox->NumSlots() >= 2);
HBox->GetSlot(1).SetVerticalAlignment(InHasArrowDown ? (EVerticalAlignment)Style->DownArrowAlign : VAlign_Center);
HBox->GetSlot(1).SetPadding(InHasArrowDown ? Style->DownArrowPadding : FMargin(0));
check(ForegroundArrowImage);
ForegroundArrowImage->SetVisibility(InHasArrowDown ? EVisibility::Visible : EVisibility::Collapsed);
check(ShadowImage);
ShadowImage->SetVisibility(InHasArrowDown && bHasDownArrowShadow ? EVisibility::Visible : EVisibility::Collapsed);
}