1478 lines
47 KiB
C++
1478 lines
47 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#pragma once
|
|
|
|
#include "CoreMinimal.h"
|
|
#include "InputCoreTypes.h"
|
|
#include "ITableRow.h"
|
|
#include "Framework/Views/ITypedTableView.h"
|
|
#include "Framework/Views/TableViewTypeTraits.h"
|
|
#include "Input/DragAndDrop.h"
|
|
#include "Input/Events.h"
|
|
#include "Input/Reply.h"
|
|
#include "Layout/Geometry.h"
|
|
#include "Layout/Margin.h"
|
|
#include "Misc/Attribute.h"
|
|
#include "Rendering/DrawElements.h"
|
|
#include "Styling/CoreStyle.h"
|
|
#include "Styling/SlateColor.h"
|
|
#include "Styling/SlateTypes.h"
|
|
#include "Types/SlateStructs.h"
|
|
#include "Widgets/DeclarativeSyntaxSupport.h"
|
|
#include "Widgets/Layout/SBorder.h"
|
|
#include "Widgets/Layout/SBox.h"
|
|
#include "Widgets/SBoxPanel.h"
|
|
#include "Widgets/SNullWidget.h"
|
|
#include "Widgets/SWidget.h"
|
|
#include "Widgets/Views/SExpanderArrow.h"
|
|
#include "Widgets/Views/SHeaderRow.h"
|
|
#include "Widgets/Views/STableViewBase.h"
|
|
#if WITH_ACCESSIBILITY
|
|
#include "GenericPlatform/Accessibility/GenericAccessibleInterfaces.h"
|
|
#include "Widgets/Accessibility/SlateCoreAccessibleWidgets.h"
|
|
#include "Widgets/Accessibility/SlateAccessibleWidgetCache.h"
|
|
#include "Widgets/Accessibility/SlateAccessibleMessageHandler.h"
|
|
#endif
|
|
|
|
template <typename ItemType> class SListView;
|
|
|
|
/**
|
|
* When the table row should signal the owner widget that the selection changed.
|
|
* This only affect the selection with the left mouse button!
|
|
*/
|
|
enum class ETableRowSignalSelectionMode
|
|
{
|
|
/**
|
|
* The selection will be updated on the left mouse button down, but the owner table will only get signaled when the mouse button is released or if a drag is detected.
|
|
*/
|
|
Deferred,
|
|
/**
|
|
* Each time the selection of the owner table is changed the table get signaled.
|
|
*/
|
|
Instantaneous
|
|
};
|
|
|
|
/**
|
|
* Where we are going to drop relative to the target item.
|
|
*/
|
|
enum class EItemDropZone
|
|
{
|
|
AboveItem,
|
|
OntoItem,
|
|
BelowItem
|
|
};
|
|
|
|
template <typename ItemType> class SListView;
|
|
|
|
DECLARE_DELEGATE_OneParam(FOnTableRowDragEnter, FDragDropEvent const&);
|
|
DECLARE_DELEGATE_OneParam(FOnTableRowDragLeave, FDragDropEvent const&);
|
|
DECLARE_DELEGATE_RetVal_OneParam(FReply, FOnTableRowDrop, FDragDropEvent const&);
|
|
|
|
|
|
/**
|
|
* The ListView is populated by Selectable widgets.
|
|
* A Selectable widget is a way of the ListView containing it (OwnerTable) and holds arbitrary Content (Content).
|
|
* A Selectable works with its corresponding ListView to provide selection functionality.
|
|
*/
|
|
template<typename ItemType>
|
|
class STableRow : public ITableRow, public SBorder
|
|
{
|
|
static_assert(TIsValidListItem<ItemType>::Value, "Item type T must be UObjectBase*, TObjectPtr<>, TWeakObjectPtr<>, TSharedRef<>, or TSharedPtr<>.");
|
|
|
|
public:
|
|
/** Delegate signature for querying whether this FDragDropEvent will be handled by the drop target of type ItemType. */
|
|
DECLARE_DELEGATE_RetVal_ThreeParams(TOptional<EItemDropZone>, FOnCanAcceptDrop, const FDragDropEvent&, EItemDropZone, ItemType);
|
|
/** Delegate signature for handling the drop of FDragDropEvent onto target of type ItemType */
|
|
DECLARE_DELEGATE_RetVal_ThreeParams(FReply, FOnAcceptDrop, const FDragDropEvent&, EItemDropZone, ItemType);
|
|
/** Delegate signature for painting drop indicators. */
|
|
DECLARE_DELEGATE_RetVal_EightParams(int32, FOnPaintDropIndicator, EItemDropZone, const FPaintArgs&, const FGeometry&, const FSlateRect&, FSlateWindowElementList&, int32, const FWidgetStyle&, bool);
|
|
public:
|
|
|
|
SLATE_BEGIN_ARGS( STableRow< ItemType > )
|
|
: _Style( &FCoreStyle::Get().GetWidgetStyle<FTableRowStyle>("TableView.Row") )
|
|
, _ExpanderStyleSet( &FCoreStyle::Get() )
|
|
, _Padding( FMargin(0) )
|
|
, _ShowSelection( true )
|
|
, _ShowWires( false )
|
|
, _bAllowPreselectedItemActivation(false)
|
|
, _SignalSelectionMode( ETableRowSignalSelectionMode::Deferred )
|
|
, _Content()
|
|
{}
|
|
|
|
SLATE_STYLE_ARGUMENT( FTableRowStyle, Style )
|
|
SLATE_ARGUMENT(const ISlateStyle*, ExpanderStyleSet)
|
|
|
|
// High Level DragAndDrop
|
|
|
|
/**
|
|
* Handle this event to determine whether a drag and drop operation can be executed on top of the target row widget.
|
|
* Most commonly, this is used for previewing re-ordering and re-organization operations in lists or trees.
|
|
* e.g. A user is dragging one item into a different spot in the list or tree.
|
|
* This delegate will be called to figure out if we should give visual feedback on whether an item will
|
|
* successfully drop into the list.
|
|
*/
|
|
SLATE_EVENT( FOnCanAcceptDrop, OnCanAcceptDrop )
|
|
|
|
/**
|
|
* Perform a drop operation onto the target row widget
|
|
* Most commonly used for executing a re-ordering and re-organization operations in lists or trees.
|
|
* e.g. A user was dragging one item into a different spot in the list; they just dropped it.
|
|
* This is our chance to handle the drop by reordering items and calling for a list refresh.
|
|
*/
|
|
SLATE_EVENT( FOnAcceptDrop, OnAcceptDrop )
|
|
|
|
/**
|
|
* Used for painting drop indicators
|
|
*/
|
|
SLATE_EVENT( FOnPaintDropIndicator, OnPaintDropIndicator )
|
|
|
|
// Low level DragAndDrop
|
|
SLATE_EVENT( FOnDragDetected, OnDragDetected )
|
|
SLATE_EVENT( FOnTableRowDragEnter, OnDragEnter )
|
|
SLATE_EVENT( FOnTableRowDragLeave, OnDragLeave )
|
|
SLATE_EVENT( FOnTableRowDrop, OnDrop )
|
|
|
|
SLATE_ATTRIBUTE( FMargin, Padding )
|
|
|
|
SLATE_ARGUMENT( bool, ShowSelection )
|
|
SLATE_ARGUMENT( bool, ShowWires)
|
|
SLATE_ARGUMENT( bool, bAllowPreselectedItemActivation)
|
|
|
|
/**
|
|
* The Signal Selection mode affect when the owner table gets notified that the selection has changed.
|
|
* This only affect the selection with the left mouse button!
|
|
* When Deferred, the owner table will get notified when the button is released or when a drag started.
|
|
* When Instantaneous, the owner table is notified as soon as the selection changed.
|
|
*/
|
|
SLATE_ARGUMENT( ETableRowSignalSelectionMode , SignalSelectionMode)
|
|
|
|
SLATE_DEFAULT_SLOT( typename STableRow<ItemType>::FArguments, Content )
|
|
|
|
SLATE_END_ARGS()
|
|
|
|
/**
|
|
* Construct this widget
|
|
*
|
|
* @param InArgs The declaration data for this widget
|
|
*/
|
|
void Construct(const typename STableRow<ItemType>::FArguments& InArgs, const TSharedRef<STableViewBase>& InOwnerTableView)
|
|
{
|
|
/** Note: Please initialize any state in ConstructInternal, not here. This is because STableRow derivatives call ConstructInternal directly to avoid constructing children. **/
|
|
|
|
ConstructInternal(InArgs, InOwnerTableView);
|
|
|
|
ConstructChildren(
|
|
InOwnerTableView->TableViewMode,
|
|
InArgs._Padding,
|
|
InArgs._Content.Widget
|
|
);
|
|
}
|
|
|
|
virtual void ConstructChildren( ETableViewMode::Type InOwnerTableMode, const TAttribute<FMargin>& InPadding, const TSharedRef<SWidget>& InContent )
|
|
{
|
|
this->Content = InContent;
|
|
InnerContentSlot = nullptr;
|
|
|
|
if ( InOwnerTableMode == ETableViewMode::List || InOwnerTableMode == ETableViewMode::Tile )
|
|
{
|
|
// We just need to hold on to this row's content.
|
|
this->ChildSlot
|
|
.Padding( InPadding )
|
|
[
|
|
InContent
|
|
];
|
|
|
|
InnerContentSlot = &ChildSlot.AsSlot();
|
|
}
|
|
else
|
|
{
|
|
// -- Row is for TreeView --
|
|
SHorizontalBox::FSlot* InnerContentSlotNativePtr = nullptr;
|
|
|
|
// Rows in a TreeView need an expander button and some indentation
|
|
this->ChildSlot
|
|
[
|
|
SNew(SHorizontalBox)
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.HAlign(HAlign_Right)
|
|
.VAlign(VAlign_Fill)
|
|
[
|
|
SAssignNew(ExpanderArrowWidget, SExpanderArrow, SharedThis(this) )
|
|
.StyleSet(ExpanderStyleSet)
|
|
.ShouldDrawWires(bShowWires)
|
|
]
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.FillWidth(1)
|
|
.Expose( InnerContentSlotNativePtr )
|
|
.Padding( InPadding )
|
|
[
|
|
InContent
|
|
]
|
|
];
|
|
|
|
InnerContentSlot = InnerContentSlotNativePtr;
|
|
}
|
|
}
|
|
|
|
#if WITH_ACCESSIBILITY
|
|
protected:
|
|
friend class FSlateAccessibleTableRow;
|
|
/**
|
|
* An accessible implementation of STableRow exposed to platform accessibility APIs.
|
|
* For subclasses of STableRow, inherit from this class and override any functions
|
|
* to give the desired behavior.
|
|
*/
|
|
class FSlateAccessibleTableRow
|
|
: public FSlateAccessibleWidget
|
|
, public IAccessibleTableRow
|
|
{
|
|
public:
|
|
FSlateAccessibleTableRow(TWeakPtr<SWidget> InWidget, EAccessibleWidgetType InWidgetType)
|
|
: FSlateAccessibleWidget(InWidget, InWidgetType)
|
|
{}
|
|
|
|
// IAccessibleWidget
|
|
virtual IAccessibleTableRow* AsTableRow()
|
|
{
|
|
return this;
|
|
}
|
|
// ~
|
|
// IAccessibleTableRow
|
|
virtual void Select() override
|
|
{
|
|
if (Widget.IsValid())
|
|
{
|
|
TSharedPtr<STableRow<ItemType>> TableRow = StaticCastSharedPtr<STableRow<ItemType>>(Widget.Pin());
|
|
if(TableRow->OwnerTablePtr.IsValid())
|
|
{
|
|
TSharedRef< ITypedTableView<ItemType> > OwnerTable = TableRow->OwnerTablePtr.Pin().ToSharedRef();
|
|
const bool bIsActive = OwnerTable->AsWidget()->HasKeyboardFocus();
|
|
|
|
if (const TObjectPtrWrapTypeOf<ItemType>* MyItemPtr = TableRow->GetItemForThis(OwnerTable))
|
|
{
|
|
const ItemType& MyItem = *MyItemPtr;
|
|
const bool bIsSelected = OwnerTable->Private_IsItemSelected(MyItem);
|
|
OwnerTable->Private_ClearSelection();
|
|
OwnerTable->Private_SetItemSelection(MyItem, true, true);
|
|
// @TODOAccessibility: Not sure if irnoring the signal selection mode will affect anything
|
|
OwnerTable->Private_SignalSelectionChanged(ESelectInfo::Direct);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
virtual void AddToSelection() override
|
|
{
|
|
// @TODOAccessibility: When multiselection is supported
|
|
}
|
|
|
|
virtual void RemoveFromSelection() override
|
|
{
|
|
// @TODOAccessibility: When multiselection is supported
|
|
}
|
|
|
|
virtual bool IsSelected() const override
|
|
{
|
|
if (Widget.IsValid())
|
|
{
|
|
TSharedPtr<STableRow<ItemType>> TableRow = StaticCastSharedPtr<STableRow<ItemType>>(Widget.Pin());
|
|
return TableRow->IsItemSelected();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
virtual TSharedPtr<IAccessibleWidget> GetOwningTable() const override
|
|
{
|
|
if (Widget.IsValid())
|
|
{
|
|
TSharedPtr<STableRow<ItemType>> TableRow = StaticCastSharedPtr<STableRow<ItemType>>(Widget.Pin());
|
|
if (TableRow->OwnerTablePtr.IsValid())
|
|
{
|
|
TSharedRef<SWidget> OwningTableWidget = TableRow->OwnerTablePtr.Pin()->AsWidget();
|
|
return FSlateAccessibleWidgetCache::GetAccessibleWidgetChecked(OwningTableWidget);
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
// ~
|
|
};
|
|
public:
|
|
virtual TSharedRef<FSlateAccessibleWidget> CreateAccessibleWidget() override
|
|
{
|
|
// @TODOAccessibility: Add support for tile table rows and tree table rows etc
|
|
// The widget type passed in should be based on the table type of the owning tabel
|
|
EAccessibleWidgetType WidgetType = EAccessibleWidgetType::ListItem;
|
|
return MakeShareable<FSlateAccessibleWidget>(new STableRow<ItemType>::FSlateAccessibleTableRow(SharedThis(this), WidgetType));
|
|
}
|
|
#endif
|
|
|
|
/** Retrieves a brush for rendering a drop indicator for the specified drop zone */
|
|
const FSlateBrush* GetDropIndicatorBrush(EItemDropZone InItemDropZone) const
|
|
{
|
|
switch (InItemDropZone)
|
|
{
|
|
case EItemDropZone::AboveItem: return &Style->DropIndicator_Above; break;
|
|
default:
|
|
case EItemDropZone::OntoItem: return &Style->DropIndicator_Onto; break;
|
|
case EItemDropZone::BelowItem: return &Style->DropIndicator_Below; break;
|
|
};
|
|
}
|
|
|
|
int32 PaintSelection( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const
|
|
{
|
|
TSharedRef< ITypedTableView<ItemType> > OwnerTable = OwnerTablePtr.Pin().ToSharedRef();
|
|
const bool bIsActive = OwnerTable->AsWidget()->HasKeyboardFocus();
|
|
|
|
if (const TObjectPtrWrapTypeOf<ItemType>* MyItemPtr = GetItemForThis(OwnerTable))
|
|
{
|
|
if (bIsActive && OwnerTable->Private_UsesSelectorFocus() && OwnerTable->Private_HasSelectorFocus(*MyItemPtr))
|
|
{
|
|
FSlateDrawElement::MakeBox(
|
|
OutDrawElements,
|
|
LayerId++,
|
|
AllottedGeometry.ToPaintGeometry(),
|
|
&Style->SelectorFocusedBrush,
|
|
ESlateDrawEffect::None,
|
|
Style->SelectorFocusedBrush.GetTint(InWidgetStyle) * InWidgetStyle.GetColorAndOpacityTint()
|
|
);
|
|
}
|
|
}
|
|
return LayerId;
|
|
}
|
|
int32 PaintBorder( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const
|
|
{
|
|
return SBorder::OnPaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled);
|
|
}
|
|
int32 PaintDropIndicator( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const
|
|
{
|
|
if (ItemDropZone.IsSet())
|
|
{
|
|
if (PaintDropIndicatorEvent.IsBound())
|
|
{
|
|
return PaintDropIndicatorEvent.Execute(ItemDropZone.GetValue(), Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled);
|
|
}
|
|
else
|
|
{
|
|
return OnPaintDropIndicator(ItemDropZone.GetValue(), Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled);
|
|
}
|
|
}
|
|
|
|
return LayerId;
|
|
}
|
|
|
|
|
|
virtual int32 OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const override
|
|
{
|
|
LayerId = PaintSelection(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled);
|
|
LayerId = PaintBorder(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled);
|
|
LayerId = PaintDropIndicator(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled);
|
|
|
|
return LayerId;
|
|
}
|
|
|
|
virtual int32 OnPaintDropIndicator( EItemDropZone InItemDropZone, const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const
|
|
{
|
|
TSharedRef< ITypedTableView<ItemType> > OwnerTable = OwnerTablePtr.Pin().ToSharedRef();
|
|
|
|
// Draw feedback for user dropping an item above, below, or onto a row.
|
|
const FSlateBrush* DropIndicatorBrush = GetDropIndicatorBrush(InItemDropZone);
|
|
|
|
if (OwnerTable->Private_GetOrientation() == Orient_Vertical)
|
|
{
|
|
FSlateDrawElement::MakeBox
|
|
(
|
|
OutDrawElements,
|
|
LayerId++,
|
|
AllottedGeometry.ToPaintGeometry(),
|
|
DropIndicatorBrush,
|
|
ESlateDrawEffect::None,
|
|
DropIndicatorBrush->GetTint(InWidgetStyle) * InWidgetStyle.GetColorAndOpacityTint()
|
|
);
|
|
}
|
|
else
|
|
{
|
|
// Reuse the drop indicator asset for horizontal, by rotating the drawn box 90 degrees.
|
|
const FVector2f LocalSize(AllottedGeometry.GetLocalSize());
|
|
const FVector2f Pivot(LocalSize * 0.5f);
|
|
const FVector2f RotatedLocalSize(LocalSize.Y, LocalSize.X);
|
|
FSlateLayoutTransform RotatedTransform(Pivot - RotatedLocalSize * 0.5f); // Make the box centered to the alloted geometry, so that it can be rotated around the center.
|
|
|
|
FSlateDrawElement::MakeRotatedBox(
|
|
OutDrawElements,
|
|
LayerId++,
|
|
AllottedGeometry.ToPaintGeometry(RotatedLocalSize, RotatedTransform),
|
|
DropIndicatorBrush,
|
|
ESlateDrawEffect::None,
|
|
-UE_HALF_PI, // 90 deg CCW
|
|
RotatedLocalSize * 0.5f, // Relative center to the flipped
|
|
FSlateDrawElement::RelativeToElement,
|
|
DropIndicatorBrush->GetTint(InWidgetStyle) * InWidgetStyle.GetColorAndOpacityTint()
|
|
);
|
|
}
|
|
|
|
return LayerId;
|
|
}
|
|
|
|
/**
|
|
* Called when a mouse button is double clicked. Override this in derived classes.
|
|
*
|
|
* @param InMyGeometry Widget geometry.
|
|
* @param InMouseEvent Mouse button event.
|
|
* @return Returns whether the event was handled, along with other possible actions.
|
|
*/
|
|
virtual FReply OnMouseButtonDoubleClick(const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent) override
|
|
{
|
|
if (InMouseEvent.GetEffectingButton() == EKeys::LeftMouseButton)
|
|
{
|
|
TSharedRef< ITypedTableView<ItemType> > OwnerTable = OwnerTablePtr.Pin().ToSharedRef();
|
|
|
|
// Only one item can be double-clicked
|
|
if (const TObjectPtrWrapTypeOf<ItemType>* MyItemPtr = GetItemForThis(OwnerTable))
|
|
{
|
|
// If we're configured to route double-click messages to the owner of the table, then
|
|
// do that here. Otherwise, we'll toggle expansion.
|
|
const bool bWasHandled = OwnerTable->Private_OnItemDoubleClicked(*MyItemPtr);
|
|
if (!bWasHandled)
|
|
{
|
|
ToggleExpansion();
|
|
}
|
|
|
|
return FReply::Handled();
|
|
}
|
|
}
|
|
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
/**
|
|
* See SWidget::OnMouseButtonDown
|
|
*
|
|
* @param MyGeometry The Geometry of the widget receiving the event.
|
|
* @param MouseEvent Information about the input event.
|
|
* @return Whether the event was handled along with possible requests for the system to take action.
|
|
*/
|
|
virtual FReply OnMouseButtonDown( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) override
|
|
{
|
|
TSharedRef< ITypedTableView<ItemType> > OwnerTable = OwnerTablePtr.Pin().ToSharedRef();
|
|
bChangedSelectionOnMouseDown = false;
|
|
bDragWasDetected = false;
|
|
|
|
if ( MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton )
|
|
{
|
|
const ESelectionMode::Type SelectionMode = GetSelectionMode();
|
|
if (SelectionMode != ESelectionMode::None)
|
|
{
|
|
if (const TObjectPtrWrapTypeOf<ItemType>* MyItemPtr = GetItemForThis(OwnerTable))
|
|
{
|
|
const ItemType& MyItem = *MyItemPtr;
|
|
const bool bIsSelected = OwnerTable->Private_IsItemSelected(MyItem);
|
|
|
|
if (SelectionMode == ESelectionMode::Multi)
|
|
{
|
|
if (MouseEvent.IsShiftDown())
|
|
{
|
|
OwnerTable->Private_SelectRangeFromCurrentTo(MyItem);
|
|
bChangedSelectionOnMouseDown = true;
|
|
if (SignalSelectionMode == ETableRowSignalSelectionMode::Instantaneous)
|
|
{
|
|
OwnerTable->Private_SignalSelectionChanged(ESelectInfo::OnMouseClick);
|
|
}
|
|
}
|
|
else if (MouseEvent.IsControlDown())
|
|
{
|
|
OwnerTable->Private_SetItemSelection(MyItem, !bIsSelected, true);
|
|
bChangedSelectionOnMouseDown = true;
|
|
if (SignalSelectionMode == ETableRowSignalSelectionMode::Instantaneous)
|
|
{
|
|
OwnerTable->Private_SignalSelectionChanged(ESelectInfo::OnMouseClick);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((bAllowPreselectedItemActivation || !bIsSelected) && !bChangedSelectionOnMouseDown)
|
|
{
|
|
OwnerTable->Private_ClearSelection();
|
|
OwnerTable->Private_SetItemSelection(MyItem, true, true);
|
|
bChangedSelectionOnMouseDown = true;
|
|
if (SignalSelectionMode == ETableRowSignalSelectionMode::Instantaneous)
|
|
{
|
|
OwnerTable->Private_SignalSelectionChanged(ESelectInfo::OnMouseClick);
|
|
}
|
|
}
|
|
|
|
return FReply::Handled()
|
|
.DetectDrag(SharedThis(this), EKeys::LeftMouseButton)
|
|
.SetUserFocus(OwnerTable->AsWidget(), EFocusCause::Mouse)
|
|
.CaptureMouse(SharedThis(this));
|
|
}
|
|
}
|
|
}
|
|
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
/**
|
|
* See SWidget::OnMouseButtonUp
|
|
*
|
|
* @param MyGeometry The Geometry of the widget receiving the event.
|
|
* @param MouseEvent Information about the input event.
|
|
* @return Whether the event was handled along with possible requests for the system to take action.
|
|
*/
|
|
virtual FReply OnMouseButtonUp( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) override
|
|
{
|
|
TSharedRef< ITypedTableView<ItemType> > OwnerTable = OwnerTablePtr.Pin().ToSharedRef();
|
|
|
|
// Requires #include "Widgets/Views/SListView.h" in your header (not done in STableRow.h to avoid circular reference).
|
|
TSharedRef< STableViewBase > OwnerTableViewBase = StaticCastSharedRef< SListView<ItemType> >(OwnerTable);
|
|
|
|
if ( MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton )
|
|
{
|
|
FReply Reply = FReply::Unhandled().ReleaseMouseCapture();
|
|
|
|
if ( bChangedSelectionOnMouseDown )
|
|
{
|
|
Reply = FReply::Handled().ReleaseMouseCapture();
|
|
}
|
|
|
|
const bool bIsUnderMouse = MyGeometry.IsUnderLocation(MouseEvent.GetScreenSpacePosition());
|
|
if ( HasMouseCapture() )
|
|
{
|
|
if ( bIsUnderMouse && !bDragWasDetected )
|
|
{
|
|
switch( GetSelectionMode() )
|
|
{
|
|
case ESelectionMode::SingleToggle:
|
|
{
|
|
if ( !bChangedSelectionOnMouseDown )
|
|
{
|
|
OwnerTable->Private_ClearSelection();
|
|
OwnerTable->Private_SignalSelectionChanged(ESelectInfo::OnMouseClick);
|
|
}
|
|
|
|
Reply = FReply::Handled().ReleaseMouseCapture();
|
|
}
|
|
break;
|
|
|
|
case ESelectionMode::Multi:
|
|
{
|
|
if ( !bChangedSelectionOnMouseDown && !MouseEvent.IsControlDown() && !MouseEvent.IsShiftDown() )
|
|
{
|
|
if (const TObjectPtrWrapTypeOf<ItemType>* MyItemPtr = GetItemForThis(OwnerTable))
|
|
{
|
|
const bool bIsSelected = OwnerTable->Private_IsItemSelected(*MyItemPtr);
|
|
if (bIsSelected && OwnerTable->Private_GetNumSelectedItems() > 1)
|
|
{
|
|
// We are mousing up on a previous selected item;
|
|
// deselect everything but this item.
|
|
|
|
OwnerTable->Private_ClearSelection();
|
|
OwnerTable->Private_SetItemSelection(*MyItemPtr, true, true);
|
|
OwnerTable->Private_SignalSelectionChanged(ESelectInfo::OnMouseClick);
|
|
|
|
Reply = FReply::Handled().ReleaseMouseCapture();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (const TObjectPtrWrapTypeOf<ItemType>* MyItemPtr = GetItemForThis(OwnerTable))
|
|
{
|
|
if (OwnerTable->Private_OnItemClicked(*MyItemPtr))
|
|
{
|
|
Reply = FReply::Handled().ReleaseMouseCapture();
|
|
}
|
|
}
|
|
|
|
if (bChangedSelectionOnMouseDown && !bDragWasDetected && (SignalSelectionMode == ETableRowSignalSelectionMode::Deferred))
|
|
{
|
|
OwnerTable->Private_SignalSelectionChanged(ESelectInfo::OnMouseClick);
|
|
}
|
|
|
|
return Reply;
|
|
}
|
|
}
|
|
else if ( MouseEvent.GetEffectingButton() == EKeys::RightMouseButton && !OwnerTableViewBase->IsRightClickScrolling() )
|
|
{
|
|
// Handle selection of items when releasing the right mouse button, but only if the user isn't actively
|
|
// scrolling the view by holding down the right mouse button.
|
|
|
|
switch( GetSelectionMode() )
|
|
{
|
|
case ESelectionMode::Single:
|
|
case ESelectionMode::SingleToggle:
|
|
case ESelectionMode::Multi:
|
|
{
|
|
// Only one item can be selected at a time
|
|
if (const TObjectPtrWrapTypeOf<ItemType>* MyItemPtr = GetItemForThis(OwnerTable))
|
|
{
|
|
const bool bIsSelected = OwnerTable->Private_IsItemSelected(*MyItemPtr);
|
|
|
|
// Select the item under the cursor
|
|
if (!bIsSelected)
|
|
{
|
|
OwnerTable->Private_ClearSelection();
|
|
OwnerTable->Private_SetItemSelection(*MyItemPtr, true, true);
|
|
OwnerTable->Private_SignalSelectionChanged(ESelectInfo::OnMouseClick);
|
|
}
|
|
|
|
OwnerTable->Private_OnItemRightClicked(*MyItemPtr, MouseEvent);
|
|
|
|
return FReply::Handled();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
virtual FReply OnTouchStarted( const FGeometry& MyGeometry, const FPointerEvent& InTouchEvent ) override
|
|
{
|
|
bProcessingSelectionTouch = true;
|
|
|
|
return
|
|
FReply::Handled()
|
|
// Drag detect because if this tap turns into a drag, we stop processing
|
|
// the selection touch.
|
|
.DetectDrag( SharedThis(this), EKeys::LeftMouseButton );
|
|
}
|
|
|
|
virtual FReply OnTouchEnded( const FGeometry& MyGeometry, const FPointerEvent& InTouchEvent ) override
|
|
{
|
|
FReply Reply = FReply::Unhandled();
|
|
|
|
if (bProcessingSelectionTouch)
|
|
{
|
|
bProcessingSelectionTouch = false;
|
|
const TSharedRef<ITypedTableView<ItemType>> OwnerTable = OwnerTablePtr.Pin().ToSharedRef();
|
|
if (const TObjectPtrWrapTypeOf<ItemType>* MyItemPtr = GetItemForThis(OwnerTable))
|
|
{
|
|
ESelectionMode::Type SelectionMode = GetSelectionMode();
|
|
if (SelectionMode != ESelectionMode::None)
|
|
{
|
|
const bool bIsSelected = OwnerTable->Private_IsItemSelected(*MyItemPtr);
|
|
if (!bIsSelected)
|
|
{
|
|
if (SelectionMode != ESelectionMode::Multi)
|
|
{
|
|
OwnerTable->Private_ClearSelection();
|
|
}
|
|
OwnerTable->Private_SetItemSelection(*MyItemPtr, true, true);
|
|
OwnerTable->Private_SignalSelectionChanged(ESelectInfo::OnMouseClick);
|
|
|
|
Reply = FReply::Handled();
|
|
}
|
|
else if (SelectionMode == ESelectionMode::SingleToggle || SelectionMode == ESelectionMode::Multi)
|
|
{
|
|
OwnerTable->Private_SetItemSelection(*MyItemPtr, true, true);
|
|
OwnerTable->Private_SignalSelectionChanged(ESelectInfo::OnMouseClick);
|
|
|
|
Reply = FReply::Handled();
|
|
}
|
|
}
|
|
|
|
if (OwnerTable->Private_OnItemClicked(*MyItemPtr))
|
|
{
|
|
Reply = FReply::Handled();
|
|
}
|
|
}
|
|
}
|
|
|
|
return Reply;
|
|
}
|
|
|
|
virtual FReply OnDragDetected( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) override
|
|
{
|
|
if (bProcessingSelectionTouch)
|
|
{
|
|
// With touch input, dragging scrolls the list while selection requires a tap.
|
|
// If we are processing a touch and it turned into a drag; pass it on to the
|
|
bProcessingSelectionTouch = false;
|
|
return FReply::Handled().CaptureMouse( OwnerTablePtr.Pin()->AsWidget() );
|
|
}
|
|
else if ( HasMouseCapture() )
|
|
{
|
|
// Avoid changing the selection on the mouse up if there was a drag
|
|
bDragWasDetected = true;
|
|
|
|
if ( bChangedSelectionOnMouseDown && SignalSelectionMode == ETableRowSignalSelectionMode::Deferred )
|
|
{
|
|
TSharedPtr< ITypedTableView<ItemType> > OwnerTable = OwnerTablePtr.Pin();
|
|
OwnerTable->Private_SignalSelectionChanged(ESelectInfo::OnMouseClick);
|
|
}
|
|
}
|
|
|
|
if (OnDragDetected_Handler.IsBound())
|
|
{
|
|
return OnDragDetected_Handler.Execute( MyGeometry, MouseEvent );
|
|
}
|
|
else
|
|
{
|
|
return FReply::Unhandled();
|
|
}
|
|
}
|
|
|
|
virtual void OnDragEnter(FGeometry const& MyGeometry, FDragDropEvent const& DragDropEvent) override
|
|
{
|
|
if (OnDragEnter_Handler.IsBound())
|
|
{
|
|
OnDragEnter_Handler.Execute(DragDropEvent);
|
|
}
|
|
}
|
|
|
|
virtual void OnDragLeave(FDragDropEvent const& DragDropEvent) override
|
|
{
|
|
ItemDropZone = TOptional<EItemDropZone>();
|
|
|
|
if (OnDragLeave_Handler.IsBound())
|
|
{
|
|
OnDragLeave_Handler.Execute(DragDropEvent);
|
|
}
|
|
}
|
|
|
|
/** @return the zone (above, onto, below) based on where the user is hovering over within the row */
|
|
EItemDropZone ZoneFromPointerPosition(UE::Slate::FDeprecateVector2DParameter LocalPointerPos, UE::Slate::FDeprecateVector2DParameter LocalSize, EOrientation Orientation)
|
|
{
|
|
const float PointerPos = Orientation == EOrientation::Orient_Horizontal ? LocalPointerPos.X : LocalPointerPos.Y;
|
|
const float Size = Orientation == EOrientation::Orient_Horizontal ? LocalSize.X : LocalSize.Y;
|
|
|
|
const float ZoneBoundarySu = FMath::Clamp(Size * 0.25f, 3.0f, 10.0f);
|
|
if (PointerPos < ZoneBoundarySu)
|
|
{
|
|
return EItemDropZone::AboveItem;
|
|
}
|
|
else if (PointerPos > Size - ZoneBoundarySu)
|
|
{
|
|
return EItemDropZone::BelowItem;
|
|
}
|
|
else
|
|
{
|
|
return EItemDropZone::OntoItem;
|
|
}
|
|
}
|
|
|
|
virtual FReply OnDragOver(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) override
|
|
{
|
|
if ( OnCanAcceptDrop.IsBound() )
|
|
{
|
|
const TSharedRef< ITypedTableView<ItemType> > OwnerTable = OwnerTablePtr.Pin().ToSharedRef();
|
|
const FVector2f LocalPointerPos = MyGeometry.AbsoluteToLocal(DragDropEvent.GetScreenSpacePosition());
|
|
const EItemDropZone ItemHoverZone = ZoneFromPointerPosition(LocalPointerPos, MyGeometry.GetLocalSize(), OwnerTable->Private_GetOrientation());
|
|
|
|
ItemDropZone = [ItemHoverZone, DragDropEvent, this]()
|
|
{
|
|
TSharedRef< ITypedTableView<ItemType> > OwnerTable = OwnerTablePtr.Pin().ToSharedRef();
|
|
if (const TObjectPtrWrapTypeOf<ItemType>* MyItemPtr = GetItemForThis(OwnerTable))
|
|
{
|
|
return OnCanAcceptDrop.Execute(DragDropEvent, ItemHoverZone, *MyItemPtr);
|
|
}
|
|
|
|
return TOptional<EItemDropZone>();
|
|
}();
|
|
|
|
return FReply::Handled();
|
|
}
|
|
else
|
|
{
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
}
|
|
|
|
virtual FReply OnDrop(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) override
|
|
{
|
|
const FReply Reply = [&]()
|
|
{
|
|
if (OnAcceptDrop.IsBound())
|
|
{
|
|
const TSharedRef< ITypedTableView<ItemType> > OwnerTable = OwnerTablePtr.Pin().ToSharedRef();
|
|
|
|
// A drop finishes the drag/drop operation, so we are no longer providing any feedback.
|
|
ItemDropZone = TOptional<EItemDropZone>();
|
|
|
|
// Find item associated with this widget.
|
|
if (const TObjectPtrWrapTypeOf<ItemType>* MyItemPtr = GetItemForThis(OwnerTable))
|
|
{
|
|
// Which physical drop zone is the drop about to be performed onto?
|
|
const FVector2f LocalPointerPos = MyGeometry.AbsoluteToLocal(DragDropEvent.GetScreenSpacePosition());
|
|
const EItemDropZone HoveredZone = ZoneFromPointerPosition(LocalPointerPos, MyGeometry.GetLocalSize(), OwnerTable->Private_GetOrientation());
|
|
|
|
// The row gets final say over which zone to drop onto regardless of physical location.
|
|
const TOptional<EItemDropZone> ReportedZone = OnCanAcceptDrop.IsBound()
|
|
? OnCanAcceptDrop.Execute(DragDropEvent, HoveredZone, *MyItemPtr)
|
|
: HoveredZone;
|
|
|
|
if (ReportedZone.IsSet())
|
|
{
|
|
FReply DropReply = OnAcceptDrop.Execute(DragDropEvent, ReportedZone.GetValue(), *MyItemPtr);
|
|
if (DropReply.IsEventHandled() && ReportedZone.GetValue() == EItemDropZone::OntoItem)
|
|
{
|
|
// Expand the drop target just in case, so that what we dropped is visible.
|
|
OwnerTable->Private_SetItemExpansion(*MyItemPtr, true);
|
|
}
|
|
|
|
return DropReply;
|
|
}
|
|
}
|
|
}
|
|
|
|
return FReply::Unhandled();
|
|
}();
|
|
|
|
// @todo slate : Made obsolete by OnAcceptDrop. Get rid of this.
|
|
if ( !Reply.IsEventHandled() && OnDrop_Handler.IsBound() )
|
|
{
|
|
return OnDrop_Handler.Execute(DragDropEvent);
|
|
}
|
|
|
|
return Reply;
|
|
}
|
|
|
|
virtual void InitializeRow() override {}
|
|
virtual void ResetRow() override {}
|
|
|
|
virtual void SetIndexInList( int32 InIndexInList ) override
|
|
{
|
|
IndexInList = InIndexInList;
|
|
}
|
|
|
|
virtual int32 GetIndexInList() override
|
|
{
|
|
return IndexInList;
|
|
}
|
|
|
|
virtual bool IsItemExpanded() const override
|
|
{
|
|
TSharedRef< ITypedTableView<ItemType> > OwnerTable = OwnerTablePtr.Pin().ToSharedRef();
|
|
if (const TObjectPtrWrapTypeOf<ItemType>* MyItemPtr = GetItemForThis(OwnerTable))
|
|
{
|
|
return OwnerTable->Private_IsItemExpanded(*MyItemPtr);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
virtual void ToggleExpansion() override
|
|
{
|
|
TSharedRef< ITypedTableView<ItemType> > OwnerTable = OwnerTablePtr.Pin().ToSharedRef();
|
|
|
|
const bool bItemHasChildren = OwnerTable->Private_DoesItemHaveChildren( IndexInList );
|
|
// Nothing to expand if row being clicked on doesn't have children
|
|
if( bItemHasChildren )
|
|
{
|
|
if (const TObjectPtrWrapTypeOf<ItemType>* MyItemPtr = GetItemForThis(OwnerTable))
|
|
{
|
|
const bool bIsItemExpanded = bItemHasChildren && OwnerTable->Private_IsItemExpanded(*MyItemPtr);
|
|
OwnerTable->Private_SetItemExpansion(*MyItemPtr, !bIsItemExpanded);
|
|
}
|
|
}
|
|
}
|
|
|
|
virtual bool IsItemSelected() const override
|
|
{
|
|
TSharedRef<ITypedTableView<ItemType>> OwnerTable = OwnerTablePtr.Pin().ToSharedRef();
|
|
if (const TObjectPtrWrapTypeOf<ItemType>* MyItemPtr = GetItemForThis(OwnerTable))
|
|
{
|
|
return OwnerTable->Private_IsItemSelected(*MyItemPtr);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
virtual int32 GetIndentLevel() const override
|
|
{
|
|
return OwnerTablePtr.Pin()->Private_GetNestingDepth( IndexInList );
|
|
}
|
|
|
|
virtual int32 DoesItemHaveChildren() const override
|
|
{
|
|
return OwnerTablePtr.Pin()->Private_DoesItemHaveChildren( IndexInList );
|
|
}
|
|
|
|
virtual TBitArray<> GetWiresNeededByDepth() const override
|
|
{
|
|
return OwnerTablePtr.Pin()->Private_GetWiresNeededByDepth(IndexInList);
|
|
}
|
|
|
|
virtual bool IsLastChild() const override
|
|
{
|
|
return OwnerTablePtr.Pin()->Private_IsLastChild(IndexInList);
|
|
}
|
|
|
|
virtual TSharedRef<SWidget> AsWidget() override
|
|
{
|
|
return SharedThis(this);
|
|
}
|
|
|
|
/** Set the entire content of this row, replacing any extra UI (such as the expander arrows for tree views) that was added by ConstructChildren */
|
|
virtual void SetRowContent(TSharedRef< SWidget > InContent)
|
|
{
|
|
this->Content = InContent;
|
|
InnerContentSlot = nullptr;
|
|
SBorder::SetContent(InContent);
|
|
}
|
|
|
|
/** Set the inner content of this row, preserving any extra UI (such as the expander arrows for tree views) that was added by ConstructChildren */
|
|
virtual void SetContent(TSharedRef< SWidget > InContent) override
|
|
{
|
|
this->Content = InContent;
|
|
|
|
if (InnerContentSlot)
|
|
{
|
|
InnerContentSlot->AttachWidget(InContent);
|
|
}
|
|
else
|
|
{
|
|
SBorder::SetContent(InContent);
|
|
}
|
|
}
|
|
|
|
/** Get the inner content of this row */
|
|
virtual TSharedPtr<SWidget> GetContent() override
|
|
{
|
|
if ( this->Content.IsValid() )
|
|
{
|
|
return this->Content.Pin();
|
|
}
|
|
else
|
|
{
|
|
return TSharedPtr<SWidget>();
|
|
}
|
|
}
|
|
|
|
virtual void Private_OnExpanderArrowShiftClicked() override
|
|
{
|
|
TSharedRef< ITypedTableView<ItemType> > OwnerTable = OwnerTablePtr.Pin().ToSharedRef();
|
|
|
|
const bool bItemHasChildren = OwnerTable->Private_DoesItemHaveChildren( IndexInList );
|
|
// Nothing to expand if row being clicked on doesn't have children
|
|
if( bItemHasChildren )
|
|
{
|
|
if (const TObjectPtrWrapTypeOf<ItemType>* MyItemPtr = GetItemForThis(OwnerTable))
|
|
{
|
|
const bool IsItemExpanded = bItemHasChildren && OwnerTable->Private_IsItemExpanded(*MyItemPtr);
|
|
OwnerTable->Private_OnExpanderArrowShiftClicked(*MyItemPtr, !IsItemExpanded);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** @return The border to be drawn around this list item */
|
|
virtual const FSlateBrush* GetBorder() const
|
|
{
|
|
TSharedRef< ITypedTableView<ItemType> > OwnerTable = OwnerTablePtr.Pin().ToSharedRef();
|
|
|
|
const bool bIsActive = OwnerTable->AsWidget()->HasKeyboardFocus();
|
|
|
|
const bool bItemHasChildren = OwnerTable->Private_DoesItemHaveChildren( IndexInList );
|
|
|
|
static FName GenericWhiteBoxBrush("GenericWhiteBox");
|
|
|
|
// @todo: Slate Style - make this part of the widget style
|
|
const FSlateBrush* WhiteBox = FCoreStyle::Get().GetBrush(GenericWhiteBoxBrush);
|
|
|
|
if (const TObjectPtrWrapTypeOf<ItemType>* MyItemPtr = GetItemForThis(OwnerTable))
|
|
{
|
|
const bool bIsSelected = OwnerTable->Private_IsItemSelected(*MyItemPtr);
|
|
const bool bIsHighlighted = OwnerTable->Private_IsItemHighlighted(*MyItemPtr);
|
|
|
|
const bool bAllowSelection = GetSelectionMode() != ESelectionMode::None;
|
|
const bool bEvenEntryIndex = (IndexInList % 2 == 0);
|
|
|
|
if (bIsSelected && bShowSelection)
|
|
{
|
|
if (bIsActive)
|
|
{
|
|
return IsHovered()
|
|
? &Style->ActiveHoveredBrush
|
|
: &Style->ActiveBrush;
|
|
}
|
|
else
|
|
{
|
|
return IsHovered()
|
|
? &Style->InactiveHoveredBrush
|
|
: &Style->InactiveBrush;
|
|
}
|
|
}
|
|
else if (!bIsSelected && bIsHighlighted)
|
|
{
|
|
if (bIsActive)
|
|
{
|
|
return IsHovered()
|
|
? (bEvenEntryIndex ? &Style->EvenRowBackgroundHoveredBrush : &Style->OddRowBackgroundHoveredBrush)
|
|
: &Style->ActiveHighlightedBrush;
|
|
}
|
|
else
|
|
{
|
|
return IsHovered()
|
|
? (bEvenEntryIndex ? &Style->EvenRowBackgroundHoveredBrush : &Style->OddRowBackgroundHoveredBrush)
|
|
: &Style->InactiveHighlightedBrush;
|
|
}
|
|
}
|
|
else if (bItemHasChildren && Style->bUseParentRowBrush && GetIndentLevel() == 0)
|
|
{
|
|
return IsHovered()
|
|
? &Style->ParentRowBackgroundHoveredBrush
|
|
: &Style->ParentRowBackgroundBrush;
|
|
}
|
|
else
|
|
{
|
|
// Add a slightly lighter background for even rows
|
|
if (bEvenEntryIndex)
|
|
{
|
|
return (IsHovered() && bAllowSelection)
|
|
? &Style->EvenRowBackgroundHoveredBrush
|
|
: &Style->EvenRowBackgroundBrush;
|
|
|
|
}
|
|
else
|
|
{
|
|
return (IsHovered() && bAllowSelection)
|
|
? &Style->OddRowBackgroundHoveredBrush
|
|
: &Style->OddRowBackgroundBrush;
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
* Callback to determine if the row is selected singularly and has keyboard focus or not
|
|
*
|
|
* @return true if selected by owning widget.
|
|
*/
|
|
bool IsSelectedExclusively() const
|
|
{
|
|
TSharedRef< ITypedTableView< ItemType > > OwnerTable = OwnerTablePtr.Pin().ToSharedRef();
|
|
|
|
if (!OwnerTable->AsWidget()->HasKeyboardFocus() || OwnerTable->Private_GetNumSelectedItems() > 1)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (const TObjectPtrWrapTypeOf<ItemType>* MyItemPtr = GetItemForThis(OwnerTable))
|
|
{
|
|
return OwnerTable->Private_IsItemSelected(*MyItemPtr);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Callback to determine if the row is selected or not
|
|
*
|
|
* @return true if selected by owning widget.
|
|
*/
|
|
bool IsSelected() const
|
|
{
|
|
TSharedRef< ITypedTableView< ItemType > > OwnerTable = OwnerTablePtr.Pin().ToSharedRef();
|
|
|
|
if (const TObjectPtrWrapTypeOf<ItemType>* MyItemPtr = GetItemForThis(OwnerTable))
|
|
{
|
|
return OwnerTable->Private_IsItemSelected(*MyItemPtr);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Callback to determine if the row is highlighted or not
|
|
*
|
|
* @return true if highlighted by owning widget.
|
|
*/
|
|
bool IsHighlighted() const
|
|
{
|
|
TSharedRef< ITypedTableView< ItemType > > OwnerTable = OwnerTablePtr.Pin().ToSharedRef();
|
|
|
|
if (const TObjectPtrWrapTypeOf<ItemType>* MyItemPtr = GetItemForThis(OwnerTable))
|
|
{
|
|
return OwnerTable->Private_IsItemHighlighted(*MyItemPtr);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/** By default, this function does nothing, it should be implemented by derived class */
|
|
virtual FVector2D GetRowSizeForColumn(const FName& InColumnName) const override
|
|
{
|
|
return FVector2D::ZeroVector;
|
|
}
|
|
|
|
void SetExpanderArrowVisibility(const EVisibility InExpanderArrowVisibility)
|
|
{
|
|
if(ExpanderArrowWidget)
|
|
{
|
|
ExpanderArrowWidget->SetVisibility(InExpanderArrowVisibility);
|
|
}
|
|
}
|
|
|
|
/** Protected constructor; SWidgets should only be instantiated via declarative syntax. */
|
|
STableRow()
|
|
: IndexInList(0)
|
|
, bShowSelection(true)
|
|
, SignalSelectionMode( ETableRowSignalSelectionMode::Deferred )
|
|
{
|
|
#if WITH_ACCESSIBILITY
|
|
// As the contents of table rows could be anything,
|
|
// Ideally, somebody would assign a custom label to each table row with non-accessible content.
|
|
// However, that's not always feasible so we want the screen reader to read out the concatenated contents of children.
|
|
// E.g If ItemType == FString, then the screen reader can just read out the contents of the text box.
|
|
AccessibleBehavior = EAccessibleBehavior::Summary;
|
|
bCanChildrenBeAccessible = true;
|
|
#endif
|
|
}
|
|
|
|
protected:
|
|
|
|
/**
|
|
* An internal method to construct and setup this row widget (purposely avoids child construction).
|
|
* Split out from Construct() so that sub-classes can invoke super construction without invoking
|
|
* ConstructChildren() (sub-classes may want to constuct their own children in their own special way).
|
|
*
|
|
* @param InArgs Declaration data for this widget.
|
|
* @param InOwnerTableView The table that this row belongs to.
|
|
*/
|
|
void ConstructInternal(FArguments const& InArgs, TSharedRef<STableViewBase> const& InOwnerTableView)
|
|
{
|
|
bProcessingSelectionTouch = false;
|
|
|
|
check(InArgs._Style);
|
|
Style = InArgs._Style;
|
|
|
|
check(InArgs._ExpanderStyleSet);
|
|
ExpanderStyleSet = InArgs._ExpanderStyleSet;
|
|
|
|
SetBorderImage(TAttribute<const FSlateBrush*>(this, &STableRow::GetBorder));
|
|
|
|
this->SetForegroundColor(TAttribute<FSlateColor>( this, &STableRow::GetForegroundBasedOnSelection ));
|
|
|
|
this->OnCanAcceptDrop = InArgs._OnCanAcceptDrop;
|
|
this->OnAcceptDrop = InArgs._OnAcceptDrop;
|
|
|
|
this->OnDragDetected_Handler = InArgs._OnDragDetected;
|
|
this->OnDragEnter_Handler = InArgs._OnDragEnter;
|
|
this->OnDragLeave_Handler = InArgs._OnDragLeave;
|
|
this->OnDrop_Handler = InArgs._OnDrop;
|
|
|
|
this->SetOwnerTableView( InOwnerTableView );
|
|
|
|
this->bShowSelection = InArgs._ShowSelection;
|
|
|
|
this->SignalSelectionMode = InArgs._SignalSelectionMode;
|
|
|
|
this->bShowWires = InArgs._ShowWires;
|
|
|
|
this->bAllowPreselectedItemActivation = InArgs._bAllowPreselectedItemActivation;
|
|
}
|
|
|
|
void SetOwnerTableView( TSharedPtr<STableViewBase> OwnerTableView )
|
|
{
|
|
// We want to cast to a ITypedTableView.
|
|
// We cast to a SListView<ItemType> because C++ doesn't know that
|
|
// being a STableView implies being a ITypedTableView.
|
|
// See SListView.
|
|
this->OwnerTablePtr = StaticCastSharedPtr< SListView<ItemType> >(OwnerTableView);
|
|
}
|
|
|
|
FSlateColor GetForegroundBasedOnSelection() const
|
|
{
|
|
const TSharedPtr< ITypedTableView<ItemType> > OwnerTable = OwnerTablePtr.Pin();
|
|
const FSlateColor& NonSelectedForeground = Style->TextColor;
|
|
const FSlateColor& SelectedForeground = Style->SelectedTextColor;
|
|
|
|
if ( !bShowSelection || !OwnerTable.IsValid() )
|
|
{
|
|
return NonSelectedForeground;
|
|
}
|
|
|
|
if (const TObjectPtrWrapTypeOf<ItemType>* MyItemPtr = GetItemForThis(OwnerTable.ToSharedRef()))
|
|
{
|
|
const bool bIsSelected = OwnerTable->Private_IsItemSelected(*MyItemPtr);
|
|
|
|
return bIsSelected
|
|
? SelectedForeground
|
|
: NonSelectedForeground;
|
|
}
|
|
|
|
return NonSelectedForeground;
|
|
}
|
|
|
|
virtual ESelectionMode::Type GetSelectionMode() const override
|
|
{
|
|
const TSharedPtr< ITypedTableView<ItemType> > OwnerTable = OwnerTablePtr.Pin();
|
|
return OwnerTable->Private_GetSelectionMode();
|
|
}
|
|
|
|
const TObjectPtrWrapTypeOf<ItemType>* GetItemForThis(const TSharedRef<ITypedTableView<ItemType>>& OwnerTable) const
|
|
{
|
|
const TObjectPtrWrapTypeOf<ItemType>* MyItemPtr = OwnerTable->Private_ItemFromWidget(this);
|
|
if (MyItemPtr)
|
|
{
|
|
return MyItemPtr;
|
|
}
|
|
else
|
|
{
|
|
checkf(OwnerTable->Private_IsPendingRefresh(), TEXT("We were unable to find the item for this widget. If it was removed from the source collection, the list should be pending a refresh."));
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
protected:
|
|
|
|
/** The list that owns this Selectable */
|
|
TWeakPtr< ITypedTableView<ItemType> > OwnerTablePtr;
|
|
|
|
/** Index of the corresponding data item in the list */
|
|
int32 IndexInList;
|
|
|
|
/** Whether or not to visually show that this row is selected */
|
|
bool bShowSelection;
|
|
|
|
/** When should we signal that selection changed for a left click */
|
|
ETableRowSignalSelectionMode SignalSelectionMode;
|
|
|
|
/** Style used to draw this table row */
|
|
const FTableRowStyle* Style;
|
|
|
|
/** The slate style to use with the expander */
|
|
const ISlateStyle* ExpanderStyleSet;
|
|
|
|
/** A pointer to the expander arrow on the row (if it exists) */
|
|
TSharedPtr<SExpanderArrow> ExpanderArrowWidget;
|
|
|
|
/** @see STableRow's OnCanAcceptDrop event */
|
|
FOnCanAcceptDrop OnCanAcceptDrop;
|
|
|
|
/** @see STableRow's OnAcceptDrop event */
|
|
FOnAcceptDrop OnAcceptDrop;
|
|
|
|
/** Optional delegate for painting drop indicators */
|
|
FOnPaintDropIndicator PaintDropIndicatorEvent;
|
|
|
|
/** Are we currently dragging/dropping over this item? */
|
|
TOptional<EItemDropZone> ItemDropZone;
|
|
|
|
/** Delegate triggered when a user starts to drag a list item */
|
|
FOnDragDetected OnDragDetected_Handler;
|
|
|
|
/** Delegate triggered when a user's drag enters the bounds of this list item */
|
|
FOnTableRowDragEnter OnDragEnter_Handler;
|
|
|
|
/** Delegate triggered when a user's drag leaves the bounds of this list item */
|
|
FOnTableRowDragLeave OnDragLeave_Handler;
|
|
|
|
/** Delegate triggered when a user's drag is dropped in the bounds of this list item */
|
|
FOnTableRowDrop OnDrop_Handler;
|
|
|
|
/** The slot that contains the inner content for this row. If this is set, SetContent populates this slot with the new content rather than replace the content wholesale */
|
|
FSlotBase* InnerContentSlot;
|
|
|
|
/** The widget in the content slot for this row */
|
|
TWeakPtr<SWidget> Content;
|
|
|
|
bool bChangedSelectionOnMouseDown;
|
|
|
|
bool bDragWasDetected;
|
|
|
|
/** Did the current a touch interaction start in this item?*/
|
|
bool bProcessingSelectionTouch;
|
|
|
|
/** When activating an item via mouse button, we generally don't allow pre-selected items to be activated */
|
|
bool bAllowPreselectedItemActivation;
|
|
|
|
private:
|
|
bool bShowWires;
|
|
};
|
|
|
|
|
|
template<typename ItemType>
|
|
class SMultiColumnTableRow : public STableRow<ItemType>
|
|
{
|
|
public:
|
|
|
|
/**
|
|
* Users of SMultiColumnTableRow would usually some piece of data associated with it.
|
|
* The type of this data is ItemType; it's the stuff that your TableView (i.e. List or Tree) is visualizing.
|
|
* The ColumnName tells you which column of the TableView we need to make a widget for.
|
|
* Make a widget and return it.
|
|
*
|
|
* @param ColumnName A unique ID for a column in this TableView; see SHeaderRow::FColumn for more info.
|
|
* @return a widget to represent the contents of a cell in this row of a TableView.
|
|
*/
|
|
virtual TSharedRef<SWidget> GenerateWidgetForColumn( const FName& InColumnName ) = 0;
|
|
|
|
/** Use this to construct the superclass; e.g. FSuperRowType::Construct( FTableRowArgs(), OwnerTableView ) */
|
|
typedef SMultiColumnTableRow< ItemType > FSuperRowType;
|
|
|
|
/** Use this to construct the superclass; e.g. FSuperRowType::Construct( FTableRowArgs(), OwnerTableView ) */
|
|
typedef typename STableRow<ItemType>::FArguments FTableRowArgs;
|
|
|
|
protected:
|
|
void Construct(const FTableRowArgs& InArgs, const TSharedRef<STableViewBase>& OwnerTableView)
|
|
{
|
|
STableRow<ItemType>::Construct(
|
|
FTableRowArgs()
|
|
.Style(InArgs._Style)
|
|
.ExpanderStyleSet(InArgs._ExpanderStyleSet)
|
|
.Padding(InArgs._Padding)
|
|
.ShowSelection(InArgs._ShowSelection)
|
|
.OnCanAcceptDrop(InArgs._OnCanAcceptDrop)
|
|
.OnAcceptDrop(InArgs._OnAcceptDrop)
|
|
.OnDragDetected(InArgs._OnDragDetected)
|
|
.OnDragEnter(InArgs._OnDragEnter)
|
|
.OnDragLeave(InArgs._OnDragLeave)
|
|
.OnDrop(InArgs._OnDrop)
|
|
.Content()
|
|
[
|
|
SAssignNew( Box, SHorizontalBox )
|
|
]
|
|
|
|
, OwnerTableView );
|
|
|
|
// Sign up for notifications about changes to the HeaderRow
|
|
TSharedPtr< SHeaderRow > HeaderRow = OwnerTableView->GetHeaderRow();
|
|
check( HeaderRow.IsValid() );
|
|
HeaderRow->OnColumnsChanged()->AddSP( this, &SMultiColumnTableRow<ItemType>::GenerateColumns );
|
|
|
|
// Populate the row with user-generated content
|
|
this->GenerateColumns( HeaderRow.ToSharedRef() );
|
|
}
|
|
|
|
virtual void ConstructChildren( ETableViewMode::Type InOwnerTableMode, const TAttribute<FMargin>& InPadding, const TSharedRef<SWidget>& InContent ) override
|
|
{
|
|
STableRow<ItemType>::Content = InContent;
|
|
|
|
// MultiColumnRows let the user decide which column should contain the expander/indenter item.
|
|
this->ChildSlot
|
|
.Padding( InPadding )
|
|
[
|
|
InContent
|
|
];
|
|
}
|
|
|
|
void GenerateColumns( const TSharedRef<SHeaderRow>& InColumnHeaders )
|
|
{
|
|
Box->ClearChildren();
|
|
const TIndirectArray<SHeaderRow::FColumn>& Columns = InColumnHeaders->GetColumns();
|
|
const int32 NumColumns = Columns.Num();
|
|
TMap< FName, TSharedRef< SWidget > > NewColumnIdToSlotContents;
|
|
|
|
for( int32 ColumnIndex = 0; ColumnIndex < NumColumns; ++ColumnIndex )
|
|
{
|
|
const SHeaderRow::FColumn& Column = Columns[ColumnIndex];
|
|
if ( InColumnHeaders->ShouldGeneratedColumn(Column.ColumnId) )
|
|
{
|
|
TSharedRef< SWidget >* ExistingWidget = ColumnIdToSlotContents.Find(Column.ColumnId);
|
|
TSharedRef< SWidget > CellContents = SNullWidget::NullWidget;
|
|
if (ExistingWidget != nullptr)
|
|
{
|
|
CellContents = *ExistingWidget;
|
|
}
|
|
else
|
|
{
|
|
CellContents = GenerateWidgetForColumn(Column.ColumnId);
|
|
}
|
|
|
|
if ( CellContents != SNullWidget::NullWidget )
|
|
{
|
|
CellContents->SetClipping(EWidgetClipping::OnDemand);
|
|
}
|
|
|
|
switch (Column.SizeRule)
|
|
{
|
|
case EColumnSizeMode::Fill:
|
|
{
|
|
TAttribute<float> WidthBinding;
|
|
WidthBinding.BindRaw(&Column, &SHeaderRow::FColumn::GetWidth);
|
|
|
|
Box->AddSlot()
|
|
.HAlign(Column.CellHAlignment)
|
|
.VAlign(Column.CellVAlignment)
|
|
.FillWidth(WidthBinding)
|
|
[
|
|
CellContents
|
|
];
|
|
}
|
|
break;
|
|
|
|
case EColumnSizeMode::Fixed:
|
|
{
|
|
Box->AddSlot()
|
|
.AutoWidth()
|
|
[
|
|
SNew(SBox)
|
|
.WidthOverride(Column.Width.Get())
|
|
.HAlign(Column.CellHAlignment)
|
|
.VAlign(Column.CellVAlignment)
|
|
.Clipping(EWidgetClipping::OnDemand)
|
|
[
|
|
CellContents
|
|
]
|
|
];
|
|
}
|
|
break;
|
|
|
|
case EColumnSizeMode::Manual:
|
|
case EColumnSizeMode::FillSized:
|
|
{
|
|
auto GetColumnWidthAsOptionalSize = [&Column]() -> FOptionalSize
|
|
{
|
|
const float DesiredWidth = Column.GetWidth();
|
|
return FOptionalSize(DesiredWidth);
|
|
};
|
|
|
|
TAttribute<FOptionalSize> WidthBinding;
|
|
WidthBinding.Bind(TAttribute<FOptionalSize>::FGetter::CreateLambda(GetColumnWidthAsOptionalSize));
|
|
|
|
Box->AddSlot()
|
|
.AutoWidth()
|
|
[
|
|
SNew(SBox)
|
|
.WidthOverride(WidthBinding)
|
|
.HAlign(Column.CellHAlignment)
|
|
.VAlign(Column.CellVAlignment)
|
|
.Clipping(EWidgetClipping::OnDemand)
|
|
[
|
|
CellContents
|
|
]
|
|
];
|
|
}
|
|
break;
|
|
|
|
default:
|
|
ensure(false);
|
|
break;
|
|
}
|
|
|
|
NewColumnIdToSlotContents.Add(Column.ColumnId, CellContents);
|
|
}
|
|
}
|
|
|
|
ColumnIdToSlotContents = NewColumnIdToSlotContents;
|
|
}
|
|
|
|
void ClearCellCache()
|
|
{
|
|
ColumnIdToSlotContents.Empty();
|
|
}
|
|
|
|
const TSharedRef<SWidget>* GetWidgetFromColumnId(const FName& ColumnId) const
|
|
{
|
|
return ColumnIdToSlotContents.Find(ColumnId);
|
|
}
|
|
|
|
private:
|
|
|
|
TSharedPtr<SHorizontalBox> Box;
|
|
TMap< FName, TSharedRef< SWidget > > ColumnIdToSlotContents;
|
|
};
|