// Copyright Epic Games, Inc. All Rights Reserved. #include "Framework/MultiBox/SWidgetBlock.h" #include "Widgets/SBoxPanel.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "Styling/ToolBarStyle.h" #include "Widgets/SToolTip.h" #include "Widgets/Images/SImage.h" FWidgetBlock::FWidgetBlock(TSharedRef InContent, const FText& InLabel, const TAttribute& InToolTipText, const FMenuEntryStyleParams& InStyleParams, const TAttribute& InIcon) : FMultiBlock(nullptr, nullptr, NAME_None, EMultiBlockType::Widget) , ContentWidget(InContent) , Label(InLabel) , ToolTipText(InToolTipText) , Icon(InIcon) , StyleParams(InStyleParams) { } void FWidgetBlock::SetCustomMenuDelegate( FNewMenuDelegate& InCustomMenuDelegate ) { CustomMenuDelegate = InCustomMenuDelegate; } void FWidgetBlock::CreateMenuEntry(FMenuBuilder& MenuBuilder) const { if ( CustomMenuDelegate.IsBound() ) { CustomMenuDelegate.Execute(MenuBuilder); } else { FText EntryLabel = (!Label.IsEmpty()) ? Label : NSLOCTEXT("WidgetBlock", "CustomControl", "Custom Control"); FMenuEntryStyleParams MenuStyleParams; MenuStyleParams.bNoIndent = true; MenuBuilder.AddWidget(ContentWidget, FText::GetEmpty(), MenuStyleParams); } } /** * Allocates a widget for this type of MultiBlock. Override this in derived classes. * * @return MultiBlock widget object */ TSharedRef< class IMultiBlockBaseWidget > FWidgetBlock::ConstructWidget() const { return SNew( SWidgetBlock ) .Cursor(EMouseCursor::Default); } bool FWidgetBlock::GetAlignmentOverrides(FMenuEntryStyleParams& OutAlignmentParameters) const { OutAlignmentParameters.HorizontalAlignment = StyleParams.HorizontalAlignment; OutAlignmentParameters.VerticalAlignment = StyleParams.VerticalAlignment.Get(VAlign_Fill); if (StyleParams.SizeRule.IsSet()) { OutAlignmentParameters.SizeRule = StyleParams.SizeRule; OutAlignmentParameters.FillSize = StyleParams.FillSize; OutAlignmentParameters.FillSizeMin = StyleParams.FillSizeMin; } else if (StyleParams.HorizontalAlignment == HAlign_Fill) { OutAlignmentParameters.SizeRule = FSizeParam::ESizeRule::SizeRule_Auto; } OutAlignmentParameters.MinimumSize = StyleParams.MinimumSize; OutAlignmentParameters.MaximumSize = StyleParams.MaximumSize; return true; } /** * Construct this widget * * @param InArgs The declaration data for this widget */ void SWidgetBlock::Construct( const FArguments& InArgs ) { } /** * Builds this MultiBlock widget up from the MultiBlock associated with it */ void SWidgetBlock::BuildMultiBlockWidget(const ISlateStyle* StyleSet, const FName& StyleName) { const TSharedPtr OwnerMultiBoxWidgetPinned = OwnerMultiBoxWidget.Pin(); TSharedPtr< const FMultiBox > MultiBox = OwnerMultiBoxWidgetPinned->GetMultiBox(); const TSharedRef WidgetBlock = StaticCastSharedRef(MultiBlock.ToSharedRef()); const bool bHasLabel = !WidgetBlock->Label.IsEmpty(); // Initially use default behavior EVerticalAlignment ContentVerticalAlignment = bHasLabel ? VAlign_Center : VAlign_Fill; // Support menus which do not have a defined widget style yet FMargin Padding; const FTextBlockStyle* LabelStyle = nullptr; if(StyleSet->HasWidgetStyle(StyleName)) { const FToolBarStyle& ToolBarStyle = StyleSet->GetWidgetStyle(StyleName); Padding = WidgetBlock->StyleParams.bNoIndent ? ToolBarStyle.BlockPadding : ToolBarStyle.IndentedBlockPadding; LabelStyle = &ToolBarStyle.LabelStyle; if (ToolBarStyle.VerticalAlignmentOverride.IsSet()) { ContentVerticalAlignment = ToolBarStyle.VerticalAlignmentOverride.GetValue(); } } else { Padding = WidgetBlock->StyleParams.bNoIndent ? StyleSet->GetMargin(StyleName, ".Block.Padding") : StyleSet->GetMargin(StyleName, ".Block.IndentedPadding"); LabelStyle = &StyleSet->GetWidgetStyle(ISlateStyle::Join(StyleName, ".Label")); } if (WidgetBlock->StyleParams.VerticalAlignment.IsSet()) { ContentVerticalAlignment = WidgetBlock->StyleParams.VerticalAlignment.GetValue(); } if(OwnerMultiBoxWidgetPinned->GetMultiBox()->GetType() == EMultiBoxType::Menu) { // Account for checkmark used in other menu blocks but not used in for widget rows Padding = Padding + FMargin(14, 0, 8, 0); // If there is no label, allow the custom menu widget to consume the entire space if (!bHasLabel) { // We use 1 pixel of padding to ensure the menu border shows up Padding = FMargin(1); } } // For searchable menus with a custom widgets without a label, find a TextBlock to connect with for search // This is similar to how SMenuEntryBlock works FText SearchLabel = WidgetBlock->Label; TAttribute SearchHighlightText; if( OwnerMultiBoxWidgetPinned->GetMultiBox()->GetType() == EMultiBoxType::Menu && OwnerMultiBoxWidgetPinned->GetSearchable()) { SearchHighlightText.Bind(OwnerMultiBoxWidgetPinned.Get(), &SMultiBoxWidget::GetSearchText); if (!bHasLabel) { TSharedRef TextBlock = FindTextBlockWidget(WidgetBlock->ContentWidget); if (TextBlock != SNullWidget::NullWidget ) { // Bind the search text to the widgets text to highlight TSharedRef TheTextBlock = StaticCastSharedRef(TextBlock); TheTextBlock->SetHighlightText(SearchHighlightText); SearchLabel = TheTextBlock.Get().GetText(); } } } const TSharedRef ThisWidget = this->AsWidget(); // Add this widget to the search list of the multibox OwnerMultiBoxWidgetPinned->AddElement(ThisWidget, SearchLabel, MultiBlock->GetSearchable()); // This widget holds the search text, set it as the search block widget if (OwnerMultiBoxWidgetPinned->GetSearchTextWidget() && OwnerMultiBoxWidgetPinned->GetSearchTextWidget()->GetParentWidget() == WidgetBlock->ContentWidget) { OwnerMultiBoxWidgetPinned->SetSearchBlockWidget(ThisWidget); } static constexpr float IconRightPadding = 4.f; // If we were supplied an image than go ahead and use that, otherwise we use a null widget TSharedRef IconWidget = SNullWidget::NullWidget; if (WidgetBlock->Icon.IsSet()) { const FSlateIcon& ActualIcon = WidgetBlock->Icon.Get(); const FSlateBrush* IconBrush = ActualIcon.GetIcon(); if (IconBrush->GetResourceName() != NAME_None) { IconWidget = SNew(SImage) .ColorAndOpacity(FSlateColor::UseSubduedForeground()) .Image(IconBrush); } } const float MenuIconSize = StyleSet->GetFloat(StyleName, ".MenuIconSize", 16.f); // clang-format off ChildSlot .Padding( Padding ) // Large left margin mimics the indent of normal menu items when bNoIndent is false [ SNew(SHorizontalBox) .ToolTip(FMultiBoxSettings::ToolTipConstructor.Execute(WidgetBlock->ToolTipText, nullptr, nullptr, /*ShowActionShortcut=*/ false )) + SHorizontalBox::Slot() .AutoWidth() .Padding(FMargin(2, 0, 6, 0)) [ SNew(SBox) .Visibility(IconWidget != SNullWidget::NullWidget ? EVisibility::Visible : EVisibility::Collapsed) .WidthOverride(MenuIconSize + 2) .HeightOverride(MenuIconSize) .HAlign(HAlign_Center) .VAlign(VAlign_Center) [ SNew(SBox) .WidthOverride(MenuIconSize) .HeightOverride(MenuIconSize) [ IconWidget ] ] ] + SHorizontalBox::Slot() .Padding(FMargin(2, 0, 6, 0)) .AutoWidth() [ SNew(SHorizontalBox) .Visibility(bHasLabel ? EVisibility::Visible : EVisibility::Collapsed) + SHorizontalBox::Slot() .AutoWidth() .Padding(bHasLabel ? FMargin(0.f, 0.f, IconRightPadding, 0.f) : FMargin(0)) .VAlign(VAlign_Center) [ SNew(STextBlock) .TextStyle(LabelStyle) .Text(WidgetBlock->Label) .HighlightText(SearchHighlightText) .ColorAndOpacity(FAppStyle::Get().GetSlateColor("Colors.ForegroundHover")) ] ] +SHorizontalBox::Slot() .VAlign(ContentVerticalAlignment) .FillWidth(1.f) [ WidgetBlock->ContentWidget ] ]; // clang-format on SetVisibility(MultiBlock->GetVisibilityOverride()); } TSharedRef SWidgetBlock::FindTextBlockWidget( TSharedRef Content ) { if (Content->GetType() == FName(TEXT("STextBlock"))) { return Content; } FChildren* Children = Content->GetChildren(); int32 NumChildren = Children->Num(); for( int32 Index = 0; Index < NumChildren; ++Index ) { TSharedRef Found = FindTextBlockWidget( Children->GetChildAt( Index )); if (Found != SNullWidget::NullWidget) { return Found; } } return SNullWidget::NullWidget; } void SWidgetBlock::OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { // If this widget is in a Menu, attempt to close any other open submenus within that menu TSharedPtr OwnerMultiBoxWidgetPinned = OwnerMultiBoxWidget.Pin(); if(OwnerMultiBoxWidgetPinned->GetMultiBox()->GetType() == EMultiBoxType::Menu) { const TSharedPtr< const SMenuAnchor > OpenedMenuAnchor( OwnerMultiBoxWidgetPinned->GetOpenMenu() ); if ( OpenedMenuAnchor.IsValid() && OpenedMenuAnchor->IsOpen() ) { OwnerMultiBoxWidgetPinned->CloseSummonedMenus(); } } SMultiBlockBaseWidget::OnMouseEnter(MyGeometry, MouseEvent); }