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

1150 lines
31 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Widgets/Views/SHeaderRow.h"
#include "Widgets/Layout/SSplitter.h"
#include "Types/SlateStructs.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/SOverlay.h"
#include "Layout/WidgetPath.h"
#include "Framework/Application/MenuStack.h"
#include "Framework/Application/SlateApplication.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "Widgets/Layout/SSpacer.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Images/SThrobber.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Layout/SScrollBar.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Input/SComboButton.h"
#include "Widgets/Layout/SSeparator.h"
#define LOCTEXT_NAMESPACE "SHeaderRow"
class STableColumnHeader : public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS(STableColumnHeader)
: _Style( &FAppStyle::Get().GetWidgetStyle<FTableColumnHeaderStyle>("TableView.Header.Column") )
{}
SLATE_STYLE_ARGUMENT( FTableColumnHeaderStyle, Style )
SLATE_END_ARGS()
STableColumnHeader()
: InitialSortMode( EColumnSortMode::Ascending )
, SortMode( EColumnSortMode::None )
, SortPriority( EColumnSortPriority::Primary )
, IsSorting( false )
, OnSortModeChanged()
, ContextMenuContent( SNullWidget::NullWidget )
, ColumnId( NAME_None )
, Style( nullptr )
{
}
/**
* Construct the widget
*
* @param InDeclaration A declaration from which to construct the widget
*/
void Construct( const STableColumnHeader::FArguments& InArgs, const SHeaderRow::FColumn& Column, const TSharedRef<SWidget>& OverrideHeaderMenuContent, const FMargin DefaultHeaderContentPadding )
{
check(InArgs._Style);
Style = InArgs._Style;
ColumnId = Column.ColumnId;
InitialSortMode = Column.InitialSortMode;
SortMode = Column.SortMode;
SortPriority = Column.SortPriority;
IsSorting = Column.IsSorting;
OnSortModeChanged = Column.OnSortModeChanged;
ContextMenuContent = OverrideHeaderMenuContent;
ComboVisibility = Column.HeaderComboVisibility;
TAttribute< FText > LabelText = Column.DefaultText;
if (Column.HeaderContent.Widget == SNullWidget::NullWidget)
{
if (!Column.DefaultText.IsSet())
{
LabelText = FText::FromString( Column.ColumnId.ToString() + TEXT("[LabelMissing]") );
}
}
TSharedPtr< SHorizontalBox > Box;
TSharedRef< SOverlay > Overlay = SNew( SOverlay );
Overlay->AddSlot( 0 )
[
SAssignNew( Box, SHorizontalBox )
];
TSharedRef< SWidget > PrimaryContent = Column.HeaderContent.Widget;
if ( PrimaryContent == SNullWidget::NullWidget )
{
PrimaryContent =
SNew( SBox )
.HeightOverride( 24.0f )
.Padding( 0.0f )
.VAlign( VAlign_Center )
[
SNew( STextBlock )
.TextStyle( FAppStyle::Get(), "NormalText" )
.Text( LabelText )
.OverflowPolicy(Column.OverflowPolicy)
];
}
if ( OnSortModeChanged.IsBound() )
{
//optional main button with the column's title. Used to toggle sorting modes.
Box->AddSlot()
.FillWidth( 1.0f )
.HAlign( HAlign_Fill )
[
SNew( SButton )
.ButtonStyle( FAppStyle::Get(), "NoBorder" )
.ForegroundColor( FSlateColor::UseForeground() )
.OnClicked( this, &STableColumnHeader::OnTitleClicked )
.ContentPadding( 0.0f )
[
SNew(SBox)
.HAlign( Column.HeaderHAlignment )
.VAlign( Column.HeaderVAlignment )
[
SNew( SHorizontalBox )
+SHorizontalBox::Slot()
.AutoWidth()
.Padding( FMargin( 0.0f ) )
[
PrimaryContent
]
+SHorizontalBox::Slot()
.AutoWidth()
.HAlign( HAlign_Left )
.VAlign( VAlign_Center)
.Padding( FMargin( 4.0f, 0.0f, 0.0f, 0.0f) )
[
SNew( SOverlay )
+SOverlay::Slot()
[
SNew( SImage )
.ColorAndOpacity( FSlateColor::UseForeground() )
.Image( this, &STableColumnHeader::GetSortingBrush )
.Visibility( this, &STableColumnHeader::GetSortModeVisibility )
]
+SOverlay::Slot()
[
SNew(SCircularThrobber)
.Radius(10.0f)
.PieceImage(FAppStyle::Get().GetBrush("Throbber.CircleChunk.Small"))
.Visibility( this, &STableColumnHeader::GetSortProcessingVisibility )
]
]
]
]
];
}
else
{
Box->AddSlot()
.FillWidth( 1.0f )
.HAlign( Column.HeaderHAlignment )
.VAlign( Column.HeaderVAlignment )
[
PrimaryContent
];
}
if( ComboVisibility != EHeaderComboVisibility::Never &&
(Column.HeaderMenuContent.Widget != SNullWidget::NullWidget ||
Column.OnGetMenuContent.IsBound()))
{
// Add Drop down menu button (only if menu content has been specified)
Box->AddSlot()
.AutoWidth()
[
SAssignNew( MenuOverlay, SOverlay )
.Visibility( this, &STableColumnHeader::GetMenuOverlayVisibility )
+SOverlay::Slot()
[
SNew( SBorder )
.Padding( FMargin( 0.0f ) )
.BorderImage( this, &STableColumnHeader::GetComboButtonBorderBrush )
[
SAssignNew( ComboButton, SComboButton )
.HasDownArrow(false)
.ButtonStyle( FAppStyle::Get(), "NoBorder" )
.ContentPadding( FMargin(0) )
.ButtonContent()
[
SNew( SSpacer )
.Size( FVector2D( 14.0f, 0 ) )
]
]
]
+SOverlay::Slot()
.HAlign( HAlign_Center )
.VAlign( VAlign_Center )
[
SNew( SBox )
.HeightOverride( 18.0f )
.Padding( FMargin( 0.0f, -2.0f ) )
[
SNew( SImage )
.Image( &Style->MenuDropdownImage )
.ColorAndOpacity( this, &STableColumnHeader::GetComboButtonTint )
.Visibility( EVisibility::HitTestInvisible )
]
]
];
if (Column.HeaderMenuContent.Widget != SNullWidget::NullWidget)
{
ComboButton->SetMenuContent( ContextMenuContent );
}
else if (Column.OnGetMenuContent.IsBound())
{
ComboButton->SetOnGetMenuContent( Column.OnGetMenuContent );
}
}
this->ChildSlot
[
SNew( SBorder )
.BorderImage( this, &STableColumnHeader::GetHeaderBackgroundBrush )
.HAlign( HAlign_Fill )
.VAlign( VAlign_Fill )
.ToolTip( Column.ToolTip )
.ToolTipText( Column.ToolTip.IsSet() ? TAttribute<FText>() :
Column.DefaultTooltip.IsSet() ? Column.DefaultTooltip :
Column.HeaderContent.Widget == SNullWidget::NullWidget ? LabelText :
TAttribute<FText>() )
.Padding( Column.HeaderContentPadding.Get( DefaultHeaderContentPadding ) )
.Clipping( EWidgetClipping::ClipToBounds )
[
Overlay
]
];
}
/** Gets initial sorting mode */
EColumnSortMode::Type GetInitialSortMode() const
{
return InitialSortMode.Get();
}
/** Sets initial sorting mode */
void SetInitialSortMode(EColumnSortMode::Type NewMode)
{
InitialSortMode = NewMode;
}
/** Gets sorting mode */
EColumnSortMode::Type GetSortMode() const
{
return SortMode.Get();
}
/** Sets sorting mode */
void SetSortMode( EColumnSortMode::Type NewMode )
{
SortMode = NewMode;
}
/** Gets sorting order */
EColumnSortPriority::Type GetSortPriority() const
{
return SortPriority.Get();
}
/** Sets sorting order */
void SetSortPriority(EColumnSortPriority::Type NewPriority)
{
SortPriority = NewPriority;
}
/** Get column id that generated this header */
FName GetColumnId() const
{
return ColumnId;
}
virtual FReply OnMouseButtonUp( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) override
{
if ( MouseEvent.GetEffectingButton() == EKeys::RightMouseButton && ContextMenuContent != SNullWidget::NullWidget )
{
OpenContextMenu( MouseEvent );
return FReply::Handled();
}
return FReply::Unhandled();
}
EVisibility GetMenuOverlayVisibility() const
{
if (ComboVisibility == EHeaderComboVisibility::OnHover)
{
if (!ComboButton.IsValid() || !(IsHovered() || ComboButton->IsOpen()))
{
return EVisibility::Collapsed;
}
}
if (ComboVisibility == EHeaderComboVisibility::Never)
{
return EVisibility::Collapsed;
}
return EVisibility::Visible;
}
UE::Slate::FDeprecateVector2DResult GetMenuOverlaySize() const
{
return MenuOverlay.IsValid() ? MenuOverlay->GetDesiredSize() : FVector2f::ZeroVector;
}
private:
const FSlateBrush* GetHeaderBackgroundBrush() const
{
if ( IsHovered() && SortMode.IsBound() )
{
return &Style->HoveredBrush;
}
return &Style->NormalBrush;
}
const FSlateBrush* GetComboButtonBorderBrush() const
{
if (ComboVisibility != EHeaderComboVisibility::Never)
{
if ( ComboButton.IsValid() && ( ComboButton->IsHovered() || ComboButton->IsOpen() ) )
{
return &Style->MenuDropdownHoveredBorderBrush;
}
if ( IsHovered() || ComboVisibility == EHeaderComboVisibility::Always )
{
return &Style->MenuDropdownNormalBorderBrush;
}
}
return FStyleDefaults::GetNoBrush();
}
FSlateColor GetComboButtonTint() const
{
if (!ComboButton.IsValid())
{
return FLinearColor::White;
}
switch (ComboVisibility)
{
case EHeaderComboVisibility::Always:
{
return FLinearColor::White;
}
case EHeaderComboVisibility::Ghosted:
if ( ComboButton->IsHovered() || ComboButton->IsOpen() )
{
return FLinearColor::White;
}
else
{
return FLinearColor::White.CopyWithNewOpacity(0.5f);
}
case EHeaderComboVisibility::OnHover:
if ( IsHovered() || ComboButton->IsHovered() || ComboButton->IsOpen() )
{
return FLinearColor::White;
}
break;
case EHeaderComboVisibility::Never:
{
return FLinearColor::White;
}
default:
break;
}
return FLinearColor::Transparent;
}
/** Gets the icon associated with the current sorting mode */
const FSlateBrush* GetSortingBrush() const
{
EColumnSortPriority::Type ColumnSortPriority = SortPriority.Get();
return ( SortMode.Get() == EColumnSortMode::Ascending ?
(ColumnSortPriority == EColumnSortPriority::Secondary ? &Style->SortSecondaryAscendingImage : &Style->SortPrimaryAscendingImage) :
(ColumnSortPriority == EColumnSortPriority::Secondary ? &Style->SortSecondaryDescendingImage : &Style->SortPrimaryDescendingImage) );
}
/** Checks if sorting mode has been selected */
EVisibility GetSortModeVisibility() const
{
EColumnSortMode::Type ColumnSortMode = SortMode.Get();
const bool bShow = ColumnSortMode != EColumnSortMode::None && !IsSorting.Get();
return bShow ? EVisibility::HitTestInvisible : EVisibility::Collapsed;
}
/** Checks if sorting is in progress */
EVisibility GetSortProcessingVisibility() const
{
return IsSorting.Get() ? EVisibility::HitTestInvisible : EVisibility::Collapsed;
}
/** Called when the column title has been clicked to change sorting mode */
FReply OnTitleClicked()
{
if ( OnSortModeChanged.IsBound() )
{
FSlateApplication::Get().CloseToolTip();
const bool bIsShiftClicked = FSlateApplication::Get().GetModifierKeys().IsShiftDown();
EColumnSortPriority::Type ColumnSortPriority = SortPriority.Get();
EColumnSortMode::Type ColumnSortMode = SortMode.Get();
if (ColumnSortMode == EColumnSortMode::None)
{
if (bIsShiftClicked && SortPriority.IsBound())
{
ColumnSortPriority = EColumnSortPriority::Secondary;
}
else
{
ColumnSortPriority = EColumnSortPriority::Primary;
}
ColumnSortMode = InitialSortMode.Get();
}
else
{
if (!bIsShiftClicked && ColumnSortPriority == EColumnSortPriority::Secondary)
{
ColumnSortPriority = EColumnSortPriority::Primary;
}
if (ColumnSortMode == EColumnSortMode::Descending)
{
ColumnSortMode = EColumnSortMode::Ascending;
}
else
{
ColumnSortMode = EColumnSortMode::Descending;
}
}
OnSortModeChanged.Execute(ColumnSortPriority, ColumnId, ColumnSortMode);
}
return FReply::Handled();
}
void OpenContextMenu(const FPointerEvent& MouseEvent)
{
if ( ContextMenuContent != SNullWidget::NullWidget )
{
FVector2f SummonLocation = MouseEvent.GetScreenSpacePosition();
FWidgetPath WidgetPath = MouseEvent.GetEventPath() != nullptr ? *MouseEvent.GetEventPath() : FWidgetPath();
FSlateApplication::Get().CloseToolTip();
FSlateApplication::Get().PushMenu(AsShared(), WidgetPath, ContextMenuContent, SummonLocation, FPopupTransitionEffect(FPopupTransitionEffect::ContextMenu));
}
}
private:
/** Initial sorting mode */
TAttribute< EColumnSortMode::Type > InitialSortMode;
/** Current sorting mode */
TAttribute< EColumnSortMode::Type > SortMode;
/** Current sorting order */
TAttribute< EColumnSortPriority::Type > SortPriority;
/** Whether or not the list under the header is being sorted. */
TAttribute< bool > IsSorting;
/** Callback triggered when sorting mode changes */
FOnSortModeChanged OnSortModeChanged;
TSharedRef< SWidget > ContextMenuContent;
TSharedPtr< SComboButton > ComboButton;
/** The visibility method of the combo button */
EHeaderComboVisibility ComboVisibility;
TSharedPtr< SOverlay > MenuOverlay;
FName ColumnId;
const FTableColumnHeaderStyle* Style;
};
void SHeaderRow::Construct( const FArguments& InArgs )
{
check(InArgs._Style);
ScrollBarThickness = FVector2f::ZeroVector;
ScrollBarVisibility = EVisibility::Collapsed;
Style = InArgs._Style;
OnGetMaxRowSizeForColumn = InArgs._OnGetMaxRowSizeForColumn;
ResizeMode = InArgs._ResizeMode;
SplitterHandleSize = Style->SplitterHandleSize;
if (InArgs._SplitterHandleSize.IsSet())
{
SplitterHandleSize = InArgs._SplitterHandleSize.GetValue();
}
bCanSelectGeneratedColumn = InArgs._CanSelectGeneratedColumn;
OnHiddenColumnsListChanged = InArgs._OnHiddenColumnsListChanged;
if ( InArgs._OnColumnsChanged.IsBound() )
{
ColumnsChanged.Add( InArgs._OnColumnsChanged );
}
SBorder::Construct( SBorder::FArguments()
.Padding( 0.f )
.BorderImage( &Style->BackgroundBrush )
.ForegroundColor( Style->ForegroundColor )
);
// Copy all the column info from the declaration
for ( FColumn* const Column : InArgs.Slots )
{
Columns.Add( Column );
Column->bIsVisible = !InArgs._HiddenColumnsList.Contains(Column->ColumnId);
}
// Generate widgets for all columns
RegenerateWidgets();
}
void SHeaderRow::ResetColumnWidths()
{
for ( int32 iColumn = 0; iColumn < Columns.Num(); iColumn++ )
{
FColumn& Column = Columns[iColumn];
Column.SetWidth( Column.DefaultWidth );
}
}
const TIndirectArray<SHeaderRow::FColumn>& SHeaderRow::GetColumns() const
{
return Columns;
}
void SHeaderRow::AddColumn( const FColumn::FArguments& NewColumnArgs )
{
SHeaderRow::FColumn* NewColumn = new SHeaderRow::FColumn( NewColumnArgs );
AddColumn( *NewColumn );
}
void SHeaderRow::AddColumn( SHeaderRow::FColumn& NewColumn )
{
int32 InsertIdx = Columns.Num();
InsertColumn( NewColumn, InsertIdx );
}
void SHeaderRow::InsertColumn( const FColumn::FArguments& NewColumnArgs, int32 InsertIdx )
{
SHeaderRow::FColumn* NewColumn = new SHeaderRow::FColumn( NewColumnArgs );
InsertColumn( *NewColumn, InsertIdx );
}
void SHeaderRow::InsertColumn( FColumn& NewColumn, int32 InsertIdx )
{
check(NewColumn.ColumnId != NAME_None);
if ( Columns.Num() > 0 && Columns[Columns.Num() - 1].ColumnId == NAME_None )
{
// Insert before the filler column, or where the filler column used to be if we replaced it.
InsertIdx--;
}
Columns.Insert( &NewColumn, InsertIdx );
ColumnsChanged.Broadcast( SharedThis( this ) );
RegenerateWidgets();
}
void SHeaderRow::RemoveColumn( const FName& InColumnId )
{
check(InColumnId != NAME_None);
for ( int32 SlotIndex=Columns.Num() - 1; SlotIndex >= 0; --SlotIndex )
{
FColumn& Column = Columns[SlotIndex];
if ( Column.ColumnId == InColumnId )
{
Columns.RemoveAt(SlotIndex);
}
}
ColumnsChanged.Broadcast( SharedThis( this ) );
RegenerateWidgets();
}
void SHeaderRow::RefreshColumns()
{
RegenerateWidgets();
}
void SHeaderRow::ClearColumns()
{
const bool bHadColumnsItems = Columns.Num() > 0;
Columns.Empty();
if (bHadColumnsItems)
{
ColumnsChanged.Broadcast( SharedThis( this ) );
}
RegenerateWidgets();
}
void SHeaderRow::SetAssociatedVerticalScrollBar( const TSharedRef< SScrollBar >& ScrollBar, const float ScrollBarSize )
{
ScrollBarThickness.X = ScrollBarSize;
ScrollBarVisibility.Bind( TAttribute< EVisibility >::FGetter::CreateSP( ScrollBar, &SScrollBar::ShouldBeVisible ) );
RegenerateWidgets();
}
void SHeaderRow::SetColumnWidth( const FName& InColumnId, float InWidth )
{
check(InColumnId != NAME_None);
for ( int32 SlotIndex=Columns.Num() - 1; SlotIndex >= 0; --SlotIndex )
{
FColumn& Column = Columns[SlotIndex];
if ( Column.ColumnId == InColumnId )
{
Column.SetWidth( InWidth );
}
}
}
FVector2D SHeaderRow::GetRowSizeForSlotIndex(int32 SlotIndex) const
{
if (Columns.IsValidIndex(SlotIndex))
{
const TSharedPtr<STableColumnHeader>& HeaderWidget = HeaderWidgets[SlotIndex];
const FColumn& Column = Columns[SlotIndex];
FVector2D HeaderSize = FVector2D(HeaderWidget->GetDesiredSize());
if (Column.HeaderMenuContent.Widget != SNullWidget::NullWidget && HeaderWidget->GetMenuOverlayVisibility() != EVisibility::Visible)
{
HeaderSize += HeaderWidget->GetMenuOverlaySize();
}
if (OnGetMaxRowSizeForColumn.IsBound())
{
// It's assume that a header is at the top, so the sizing is for the width
FVector2D MaxChildColumnSize = OnGetMaxRowSizeForColumn.Execute(Column.ColumnId, EOrientation::Orient_Horizontal);
return MaxChildColumnSize.Component(EOrientation::Orient_Horizontal) < HeaderSize.Component(EOrientation::Orient_Horizontal) ? HeaderSize : MaxChildColumnSize;
}
}
return FVector2D::ZeroVector;
}
TArray<FName> SHeaderRow::GetHiddenColumnIds() const
{
TArray<FName> Result;
Result.Reserve(Columns.Num());
for (const FColumn& SomeColumn : Columns)
{
if (!SomeColumn.bIsVisible)
{
Result.Add(SomeColumn.ColumnId);
}
}
return Result;
}
bool SHeaderRow::ShouldGeneratedColumn(const FName& InColumnId) const
{
for (const FColumn& SomeColumn : Columns)
{
if (SomeColumn.ColumnId == InColumnId)
{
return SomeColumn.ShouldGenerateWidget.Get(true) && SomeColumn.bIsVisible;
}
}
return false;
}
bool SHeaderRow::IsColumnGenerated(const FName& InColumnId) const
{
for (const TSharedPtr<STableColumnHeader>& ColumnHeader : HeaderWidgets)
{
if (ColumnHeader->GetColumnId() == InColumnId)
{
return true;
}
}
return false;
}
bool SHeaderRow::IsColumnVisible(const FName& InColumnId) const
{
for (const FColumn& SomeColumn : Columns)
{
if (SomeColumn.ColumnId == InColumnId)
{
return SomeColumn.bIsVisible;
}
}
return false;
}
FReply SHeaderRow::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
if (bCanSelectGeneratedColumn && MouseEvent.GetEffectingButton() == EKeys::RightMouseButton)
{
FVector2f SummonLocation = MouseEvent.GetScreenSpacePosition();
FWidgetPath WidgetPath = MouseEvent.GetEventPath() != nullptr ? *MouseEvent.GetEventPath() : FWidgetPath();
const bool CloseAfterSelection = true;
FMenuBuilder MenuBuilder(CloseAfterSelection, nullptr);
OnGenerateSelectColumnsSubMenu(MenuBuilder);
FSlateApplication::Get().CloseToolTip();
FSlateApplication::Get().PushMenu(AsShared(), WidgetPath, MenuBuilder.MakeWidget(), SummonLocation, FPopupTransitionEffect(FPopupTransitionEffect::ContextMenu));
return FReply::Handled();
}
return FReply::Unhandled();
}
void SHeaderRow::RegenerateWidgets()
{
const float SplitterHandleDetectionSize = SplitterHandleSize > 0.0f ? SplitterHandleSize + 4.0f : 5.0f;
HeaderWidgets.Empty();
TSharedPtr<SSplitter> Splitter;
TSharedRef< SHorizontalBox > HeaderContent =
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.FillWidth( 1.0f )
[
SAssignNew(Splitter, SSplitter)
.Style( &Style->ColumnSplitterStyle )
.ResizeMode(ResizeMode)
.PhysicalSplitterHandleSize( SplitterHandleSize )
.HitDetectionSplitterHandleSize( SplitterHandleDetectionSize )
.OnGetMaxSlotSize(this, &SHeaderRow::GetRowSizeForSlotIndex)
]
+SHorizontalBox::Slot()
.AutoWidth()
.Padding( 0 )
[
SNew( SSpacer )
.Size( ScrollBarThickness )
.Visibility( ScrollBarVisibility )
];
// Construct widgets for all columns
{
const float HalfSplitterDetectionSize = ( SplitterHandleDetectionSize + 2 ) / 2;
int32 LastSlotIndex = Columns.Num() - 1;
for ( ; LastSlotIndex >= 0; --LastSlotIndex )
{
const FColumn& SomeColumn = Columns[LastSlotIndex];
if ( SomeColumn.ShouldGenerateWidget.Get(true) && SomeColumn.bIsVisible )
{
break;
}
}
// Populate the slot with widgets that represent the columns.
TSharedPtr<STableColumnHeader> NewlyMadeHeader;
for ( int32 SlotIndex=0; SlotIndex < Columns.Num(); ++SlotIndex )
{
FColumn& SomeColumn = Columns[SlotIndex];
if ( SomeColumn.ShouldGenerateWidget.Get(true) && SomeColumn.bIsVisible )
{
// Keep track of the last header we created.
TSharedPtr<STableColumnHeader> PrecedingHeader = NewlyMadeHeader;
NewlyMadeHeader.Reset();
FMargin DefaultPadding = FMargin(HalfSplitterDetectionSize, 0, HalfSplitterDetectionSize, 0);
TSharedRef<SWidget> HeaderMenuContent = SomeColumn.HeaderMenuContent.Widget;
if ( bCanSelectGeneratedColumn && HeaderMenuContent != SNullWidget::NullWidget )
{
static constexpr bool bCloseAfterSelection = true;
static constexpr bool bNoIndent = true;
FMenuEntryStyleParams StyleParams;
StyleParams.bNoIndent = bNoIndent;
FMenuBuilder MenuBuilder(bCloseAfterSelection, nullptr);
MenuBuilder.AddWidget(SomeColumn.HeaderMenuContent.Widget, FText::GetEmpty(), StyleParams);
MenuBuilder.AddMenuSeparator();
MenuBuilder.AddSubMenu(
LOCTEXT("SelectColumns", "Select Columns"),
LOCTEXT("SelectColumns_Tooltip", "Show/hide columns"),
FNewMenuDelegate::CreateSP(this, &SHeaderRow::OnGenerateSelectColumnsSubMenu)
);
HeaderMenuContent = MenuBuilder.MakeWidget();
}
TSharedRef<STableColumnHeader> NewHeader =
SAssignNew(NewlyMadeHeader, STableColumnHeader, SomeColumn, HeaderMenuContent, DefaultPadding)
.Style((SlotIndex == LastSlotIndex) ? &Style->LastColumnStyle : &Style->ColumnStyle);
HeaderWidgets.Add(NewlyMadeHeader);
switch (SomeColumn.SizeRule)
{
case EColumnSizeMode::Fill:
{
TAttribute<float> WidthBinding;
WidthBinding.BindRaw(&SomeColumn, &FColumn::GetWidth);
// Add resizable cell
Splitter->AddSlot()
.Value(WidthBinding)
.SizeRule(SSplitter::FractionOfParent)
.OnSlotResized(SSplitter::FOnSlotResized::CreateRaw(&SomeColumn, &FColumn::SetWidth))
[
NewHeader
];
}
break;
case EColumnSizeMode::Fixed:
{
// Add fixed size cell
Splitter->AddSlot()
.SizeRule(SSplitter::SizeToContent)
[
SNew(SBox)
.WidthOverride(SomeColumn.GetWidth())
[
NewHeader
]
];
}
break;
case EColumnSizeMode::Manual:
{
// Sizing grip to put at the end of the column - we can't use a SSplitter here as it doesn't have the resizing behavior we need
const float GripSize = SplitterHandleSize > 0.0f ? SplitterHandleSize + 4.0f : 5.0f;
TSharedRef<SBorder> SizingGrip = SNew(SBorder)
.Padding(0.0f)
.BorderImage( nullptr )
.Cursor(EMouseCursor::ResizeLeftRight)
.Content()
[
SNew(SSpacer)
.Size(FVector2D(GripSize, GripSize))
];
TWeakPtr<SBorder> WeakSizingGrip = SizingGrip;
auto SizingGrip_OnMouseButtonDown = [&SomeColumn, WeakSizingGrip](const FGeometry&, const FPointerEvent&) -> FReply
{
TSharedPtr<SBorder> SizingGripPtr = WeakSizingGrip.Pin();
if (SizingGripPtr.IsValid())
{
return FReply::Handled().CaptureMouse(SizingGripPtr.ToSharedRef());
}
return FReply::Unhandled();
};
auto SizingGrip_OnMouseButtonUp = [&SomeColumn, WeakSizingGrip](const FGeometry&, const FPointerEvent&) -> FReply
{
TSharedPtr<SBorder> SizingGripPtr = WeakSizingGrip.Pin();
if (SizingGripPtr.IsValid() && SizingGripPtr->HasMouseCapture())
{
return FReply::Handled().ReleaseMouseCapture();
}
return FReply::Unhandled();
};
auto SizingGrip_OnMouseMove = [&SomeColumn, WeakSizingGrip](const FGeometry&, const FPointerEvent& InPointerEvent) -> FReply
{
TSharedPtr<SBorder> SizingGripPtr = WeakSizingGrip.Pin();
if (SizingGripPtr.IsValid() && SizingGripPtr->HasMouseCapture())
{
// The sizing grip has been moved, so update our columns size from the movement delta
const float NewWidth = SomeColumn.GetWidth() + InPointerEvent.GetCursorDelta().X;
SomeColumn.SetWidth(FMath::Max(20.0f, NewWidth));
return FReply::Handled();
}
return FReply::Unhandled();
};
// Bind the events to handle the drag sizing
SizingGrip->SetOnMouseButtonDown(FPointerEventHandler::CreateLambda(SizingGrip_OnMouseButtonDown));
SizingGrip->SetOnMouseButtonUp(FPointerEventHandler::CreateLambda(SizingGrip_OnMouseButtonUp));
SizingGrip->SetOnMouseMove(FPointerEventHandler::CreateLambda(SizingGrip_OnMouseMove));
SizingGrip->SetOnMouseDoubleClick(SomeColumn.OnColumnSplitterDoubleClick);
auto GetColumnWidthAsOptionalSize = [&SomeColumn, SplitterHandleSizeCopy = SplitterHandleSize]() -> FOptionalSize
{
// Subtract SplitterHandleSize to compensate for SSplitter adding a handle between items.
const float DesiredWidth = SomeColumn.GetWidth() - SplitterHandleSizeCopy;
return FOptionalSize(DesiredWidth);
};
TAttribute<FOptionalSize> WidthBinding;
WidthBinding.Bind(TAttribute<FOptionalSize>::FGetter::CreateLambda(GetColumnWidthAsOptionalSize));
// Add resizable cell
Splitter->AddSlot()
.SizeRule(SSplitter::SizeToContent)
.Resizable(false)
[
SNew(SBox)
.WidthOverride(WidthBinding)
[
SNew(SOverlay)
+ SOverlay::Slot()
[
NewHeader
]
+ SOverlay::Slot()
.HAlign(HAlign_Right)
[
SizingGrip
]
]
];
}
break;
case EColumnSizeMode::FillSized:
{
auto GetColumnWidthAsOptionalSize = [&SomeColumn]() -> FOptionalSize
{
const float DesiredWidth = SomeColumn.GetWidth();
return FOptionalSize(DesiredWidth);
};
TAttribute<FOptionalSize> WidthBinding;
WidthBinding.Bind(TAttribute<FOptionalSize>::FGetter::CreateLambda(GetColumnWidthAsOptionalSize));
Splitter->AddSlot()
.SizeRule(SSplitter::SizeToContent)
.Resizable(true)
.OnSlotResized(SSplitter::FOnSlotResized::CreateRaw(&SomeColumn, &FColumn::SetWidth))
[
SNew(SBox)
.WidthOverride(WidthBinding)
[
NewHeader
]
];
}
break;
default:
ensure(false);
break;
}
}
}
}
if(Style->HorizontalSeparatorBrush.GetDrawType() != ESlateBrushDrawType::NoDrawType && Style->HorizontalSeparatorThickness > 0)
{
// Create a box to contain widgets for each column
SetContent(
SNew(SVerticalBox)
+ SVerticalBox::Slot()
[
HeaderContent
]
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SSeparator)
.Thickness(Style->HorizontalSeparatorThickness)
.SeparatorImage(&Style->HorizontalSeparatorBrush)
]);
}
else
{
SetContent(HeaderContent);
}
}
void SHeaderRow::ToggleAllColumns()
{
ECheckBoxState CurrentState = GetSelectAllColumnsCheckState();
bool bShouldSelectAll = CurrentState == ECheckBoxState::Unchecked || CurrentState == ECheckBoxState::Undetermined;
for (FColumn& SomeColumn : Columns)
{
if (!SomeColumn.ShouldGenerateWidget.IsSet())
{
SomeColumn.bIsVisible = bShouldSelectAll;
}
}
RefreshColumns();
ColumnsChanged.Broadcast(SharedThis(this));
OnHiddenColumnsListChanged.ExecuteIfBound();
}
bool SHeaderRow::CanToggleAllColumns() const
{
return true;
}
ECheckBoxState SHeaderRow::GetSelectAllColumnsCheckState() const
{
bool bAnyColumnsVisible = false;
bool bAnyColumnsInvisible = false;
for (const FColumn& SomeColumn : Columns)
{
if (!SomeColumn.ShouldGenerateWidget.IsSet())
{
if(SomeColumn.bIsVisible)
{
bAnyColumnsVisible = true;
}
else
{
bAnyColumnsInvisible = true;
}
}
}
if(bAnyColumnsVisible && bAnyColumnsInvisible)
{
return ECheckBoxState::Undetermined;
}
if(bAnyColumnsVisible)
{
return ECheckBoxState::Checked;
}
return ECheckBoxState::Unchecked;
}
FText SHeaderRow::GetToggleAllColumnsText() const
{
ECheckBoxState CurrentState = GetSelectAllColumnsCheckState();
if(CurrentState == ECheckBoxState::Checked)
{
return LOCTEXT("DeselectAllColumns", "Select: None");
}
else if(CurrentState == ECheckBoxState::Unchecked || CurrentState == ECheckBoxState::Undetermined)
{
return LOCTEXT("SelectAllColumns", "Select: All");
}
return FText::GetEmpty();
}
void SHeaderRow::OnGenerateSelectColumnsSubMenu(FMenuBuilder& InSubMenuBuilder)
{
InSubMenuBuilder.AddMenuEntry(
TAttribute<FText>::CreateSP(this, &SHeaderRow::GetToggleAllColumnsText),
FText::GetEmpty(),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateSP(this, &SHeaderRow::ToggleAllColumns),
FCanExecuteAction::CreateSP(this, &SHeaderRow::CanToggleAllColumns),
FGetActionCheckState::CreateSP(this, &SHeaderRow::GetSelectAllColumnsCheckState)
),
NAME_None,
EUserInterfaceActionType::ToggleButton);
for (const FColumn& SomeColumn : Columns)
{
const bool bCanExecuteAction = !SomeColumn.ShouldGenerateWidget.IsSet();
const FName ColumnId = SomeColumn.ColumnId;
InSubMenuBuilder.AddMenuEntry(
SomeColumn.DefaultText,
FText::GetEmpty(),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateSP(this, &SHeaderRow::ToggleGeneratedColumn, ColumnId),
FCanExecuteAction::CreateLambda([bCanExecuteAction] () { return bCanExecuteAction; }),
FGetActionCheckState::CreateSP(this, &SHeaderRow::GetGeneratedColumnCheckedState, ColumnId)
),
NAME_None,
EUserInterfaceActionType::ToggleButton);
}
}
void SHeaderRow::ToggleGeneratedColumn(FName ColumnId)
{
// Only column that doesn't have a ShouldGenerateWidget, can be toggled
for (FColumn& SomeColumn : Columns)
{
if (SomeColumn.ColumnId == ColumnId)
{
if (!SomeColumn.ShouldGenerateWidget.IsSet())
{
SomeColumn.bIsVisible = !SomeColumn.bIsVisible;
RefreshColumns();
ColumnsChanged.Broadcast(SharedThis(this));
OnHiddenColumnsListChanged.ExecuteIfBound();
}
break;
}
}
}
ECheckBoxState SHeaderRow::GetGeneratedColumnCheckedState(FName ColumnId) const
{
return IsColumnGenerated(ColumnId) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
}
void SHeaderRow::SetShowGeneratedColumn(const FName& ColumnId, bool InShow)
{
// Only column that doesn't have a ShouldGenerateWidget, can be toggled
for (FColumn& SomeColumn : Columns)
{
if (SomeColumn.ColumnId == ColumnId)
{
if (!SomeColumn.ShouldGenerateWidget.IsSet())
{
if (SomeColumn.bIsVisible != InShow)
{
SomeColumn.bIsVisible = !SomeColumn.bIsVisible;
RefreshColumns();
ColumnsChanged.Broadcast(SharedThis(this));
OnHiddenColumnsListChanged.ExecuteIfBound();
}
}
break;
}
}
}
#undef LOCTEXT_NAMESPACE