1406 lines
42 KiB
C++
1406 lines
42 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "Widgets/Views/STableViewBase.h"
|
|
#include "Rendering/DrawElements.h"
|
|
#include "Types/SlateConstants.h"
|
|
#include "Types/SlateStructs.h"
|
|
#include "Widgets/DeclarativeSyntaxSupport.h"
|
|
#include "Widgets/SBoxPanel.h"
|
|
#include "Styling/SlateTypes.h"
|
|
#include "Styling/CoreStyle.h"
|
|
#include "Layout/WidgetPath.h"
|
|
#include "Framework/Application/MenuStack.h"
|
|
#include "Framework/Application/SlateApplication.h"
|
|
#include "Widgets/Layout/SBox.h"
|
|
#include "Widgets/Layout/SScrollBar.h"
|
|
#include "Framework/Layout/Overscroll.h"
|
|
#include "Widgets/Views/STableRow.h"
|
|
#include "Widgets/Views/SListPanel.h"
|
|
#include "Widgets/Images/SImage.h"
|
|
|
|
#include UE_INLINE_GENERATED_CPP_BY_NAME(STableViewBase)
|
|
|
|
int32 MaxNumberOfDeltaTimes = 20;
|
|
FAutoConsoleVariableRef CVarMaxNumberOfDeltaTimes(
|
|
TEXT("Slate.InertialScrolling.MaxNumberOfDeltaTimes"),
|
|
MaxNumberOfDeltaTimes,
|
|
TEXT("The number of DeltaTimes used to calculate the smooth DeltaTime used for animated scrolling."),
|
|
ECVF_Default);
|
|
|
|
float RatioOfOutliers = 0.4f;
|
|
FAutoConsoleVariableRef CVarRatioOfOutliers(
|
|
TEXT("Slate.InertialScrolling.RatioOfOutliers"),
|
|
RatioOfOutliers,
|
|
TEXT("The ratio of outliers to remove from DeltaTimeQueue for the smooth DeltaTime calculation."),
|
|
ECVF_Default);
|
|
|
|
namespace ListConstants
|
|
{
|
|
static const float OvershootMax = 150.0f;
|
|
static const float OvershootBounceRate = 250.0f;
|
|
}
|
|
|
|
|
|
FTableViewDimensions::FTableViewDimensions(EOrientation InOrientation)
|
|
: Orientation(InOrientation)
|
|
{
|
|
}
|
|
|
|
|
|
FTableViewDimensions::FTableViewDimensions(EOrientation InOrientation, float X, float Y)
|
|
: FTableViewDimensions(InOrientation, FVector2f(X, Y))
|
|
{
|
|
}
|
|
|
|
FTableViewDimensions::FTableViewDimensions(EOrientation InOrientation, const UE::Slate::FDeprecateVector2DParameter& Size)
|
|
: FTableViewDimensions(InOrientation)
|
|
{
|
|
if (InOrientation == Orient_Vertical)
|
|
{
|
|
LineAxis = Size.X;
|
|
ScrollAxis = Size.Y;
|
|
}
|
|
else
|
|
{
|
|
ScrollAxis = Size.X;
|
|
LineAxis = Size.Y;
|
|
}
|
|
}
|
|
|
|
TSharedRef<SWidget> STableViewBase::ConstructVerticalShadowBox(TSharedRef<SWidget> Content)
|
|
{
|
|
return SNew(SOverlay)
|
|
+ SOverlay::Slot()
|
|
.Padding(0.0)
|
|
[
|
|
Content
|
|
]
|
|
+ SOverlay::Slot()
|
|
.HAlign(HAlign_Fill)
|
|
.VAlign(VAlign_Top)
|
|
[
|
|
// Shadow: Hint to scroll up
|
|
SNew(SImage)
|
|
.Visibility(EVisibility::HitTestInvisible)
|
|
.ColorAndOpacity(this, &STableViewBase::GetStartShadowOpacity)
|
|
.Image(&ShadowBoxStyle->TopShadowBrush)
|
|
]
|
|
+ SOverlay::Slot()
|
|
.HAlign(HAlign_Fill)
|
|
.VAlign(VAlign_Bottom)
|
|
[
|
|
// Shadow: a hint to scroll down
|
|
SNew(SImage)
|
|
.Visibility(EVisibility::HitTestInvisible)
|
|
.ColorAndOpacity(this, &STableViewBase::GetEndShadowOpacity)
|
|
.Image(&ShadowBoxStyle->BottomShadowBrush)
|
|
];
|
|
}
|
|
|
|
TSharedRef<SWidget> STableViewBase::ConstructHorizontalShadowBox(TSharedRef<SWidget> Content)
|
|
{
|
|
return SNew(SOverlay)
|
|
+ SOverlay::Slot()
|
|
.Padding(0.0)
|
|
[
|
|
Content
|
|
]
|
|
+ SOverlay::Slot()
|
|
.HAlign(HAlign_Left)
|
|
.VAlign(VAlign_Fill)
|
|
[
|
|
// Shadow: Hint to left
|
|
SNew(SImage)
|
|
.Visibility(EVisibility::HitTestInvisible)
|
|
.ColorAndOpacity(this, &STableViewBase::GetStartShadowOpacity)
|
|
.Image(&ShadowBoxStyle->LeftShadowBrush)
|
|
]
|
|
+ SOverlay::Slot()
|
|
.HAlign(HAlign_Right)
|
|
.VAlign(VAlign_Fill)
|
|
[
|
|
// Shadow: a hint to scroll right
|
|
SNew(SImage)
|
|
.Visibility(EVisibility::HitTestInvisible)
|
|
.ColorAndOpacity(this, &STableViewBase::GetEndShadowOpacity)
|
|
.Image(&ShadowBoxStyle->RightShadowBrush)
|
|
];
|
|
}
|
|
|
|
void STableViewBase::ConstructChildren( const TAttribute<float>& InItemWidth, const TAttribute<float>& InItemHeight, const TAttribute<EListItemAlignment>& InItemAlignment, const TSharedPtr<SHeaderRow>& InHeaderRow, const TSharedPtr<SScrollBar>& InScrollBar, EOrientation InScrollOrientation, const FOnTableViewScrolled& InOnTableViewScrolled, const FScrollBarStyle* InScrollBarStyle, const bool bInPreventThrottling )
|
|
{
|
|
bItemsNeedRefresh = true;
|
|
|
|
HeaderRow = InHeaderRow;
|
|
|
|
OnTableViewScrolled = InOnTableViewScrolled;
|
|
|
|
Orientation = InHeaderRow ? Orient_Vertical : InScrollOrientation;
|
|
UE_CLOG(InScrollOrientation != Orientation, LogSlate, Error, TEXT("STableViewBase does not support horizontal scrolling when displaying a header row"));
|
|
|
|
ItemsPanel = SNew(SListPanel)
|
|
.Clipping(GetClipping())
|
|
.ItemWidth(InItemWidth)
|
|
.ItemHeight(InItemHeight)
|
|
.NumDesiredItems(this, &STableViewBase::GetNumItemsBeingObserved)
|
|
.ItemAlignment(InItemAlignment)
|
|
.ListOrientation(Orientation);
|
|
|
|
PinnedItemsPanel = SNew(SListPanel)
|
|
.Clipping(GetClipping())
|
|
.ItemWidth(InItemWidth)
|
|
.ItemHeight(InItemHeight)
|
|
.NumDesiredItems(this, &STableViewBase::GetNumPinnedItems)
|
|
.ItemAlignment(InItemAlignment)
|
|
.ListOrientation(Orientation)
|
|
.Visibility(EVisibility::Collapsed);
|
|
|
|
TSharedPtr<SWidget> ListAndScrollbar;
|
|
if (InScrollBar)
|
|
{
|
|
// The user provided us with a scrollbar; we will rely on it.
|
|
ScrollBar = InScrollBar;
|
|
ScrollBar->SetOnUserScrolled(FOnUserScrolled::CreateSP(this, &STableViewBase::ScrollBar_OnUserScrolled));
|
|
|
|
ListAndScrollbar = ItemsPanel;
|
|
}
|
|
else
|
|
{
|
|
ScrollBar = SNew(SScrollBar)
|
|
.OnUserScrolled(this, &STableViewBase::ScrollBar_OnUserScrolled)
|
|
.Orientation(Orientation)
|
|
.Style(InScrollBarStyle ? InScrollBarStyle : &FAppStyle::Get().GetWidgetStyle<FScrollBarStyle>("ScrollBar"))
|
|
.PreventThrottling(bInPreventThrottling);
|
|
|
|
const FOptionalSize ScrollBarSize(InScrollBarStyle ? InScrollBarStyle->Thickness + (SScrollBar::DefaultUniformPadding * 2.0f) : 16.f);
|
|
|
|
// We create the itemsPanel container
|
|
TSharedRef<SWidget> PanelContainer = SNew(SVerticalBox)
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
PinnedItemsPanel.ToSharedRef()
|
|
]
|
|
+SVerticalBox::Slot()
|
|
.FillHeight(1)
|
|
[
|
|
ItemsPanel.ToSharedRef()
|
|
];
|
|
|
|
// If we use Shadow Box Style, wrap the Container in a Shadow Box.
|
|
if (bShouldUseShadowBoxStyle && ShadowBoxStyle)
|
|
{
|
|
if (Orientation == Orient_Vertical)
|
|
{
|
|
PanelContainer = ConstructVerticalShadowBox(PanelContainer);
|
|
}
|
|
else if (Orientation == Orient_Horizontal)
|
|
{
|
|
PanelContainer = ConstructHorizontalShadowBox(PanelContainer);
|
|
}
|
|
}
|
|
|
|
if (Orientation == Orient_Vertical)
|
|
{
|
|
VerticalScrollBarSlot = nullptr;
|
|
ListAndScrollbar = SNew(SHorizontalBox)
|
|
+SHorizontalBox::Slot()
|
|
.FillWidth(1)
|
|
[
|
|
PanelContainer
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(ScrollBarSlotPadding)
|
|
.Expose(VerticalScrollBarSlot)
|
|
[
|
|
SNew(SBox)
|
|
.WidthOverride(ScrollBarSize)
|
|
[
|
|
ScrollBar.ToSharedRef()
|
|
]
|
|
];
|
|
}
|
|
else
|
|
{
|
|
HorizontalScrollBarSlot = nullptr;
|
|
ListAndScrollbar = SNew(SVerticalBox)
|
|
+SVerticalBox::Slot()
|
|
.FillHeight(1)
|
|
[
|
|
PanelContainer
|
|
]
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(ScrollBarSlotPadding)
|
|
.Expose(HorizontalScrollBarSlot)
|
|
[
|
|
SNew(SBox)
|
|
.HeightOverride(ScrollBarSize)
|
|
[
|
|
ScrollBar.ToSharedRef()
|
|
]
|
|
];
|
|
}
|
|
}
|
|
|
|
if (InHeaderRow)
|
|
{
|
|
// Only associate the scrollbar if we created it.
|
|
// If the scrollbar was passed in from outside then it won't appear under our header row so doesn't need compensating for.
|
|
if (!InScrollBar)
|
|
{
|
|
InHeaderRow->SetAssociatedVerticalScrollBar(ScrollBar.ToSharedRef(), 16.f);
|
|
}
|
|
|
|
this->ChildSlot
|
|
[
|
|
SNew(SVerticalBox)
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
InHeaderRow.ToSharedRef()
|
|
]
|
|
+SVerticalBox::Slot()
|
|
.FillHeight(1)
|
|
[
|
|
ListAndScrollbar.ToSharedRef()
|
|
]
|
|
];
|
|
}
|
|
else
|
|
{
|
|
this->ChildSlot
|
|
[
|
|
ListAndScrollbar.ToSharedRef()
|
|
];
|
|
}
|
|
|
|
}
|
|
|
|
bool STableViewBase::SupportsKeyboardFocus() const
|
|
{
|
|
// The ListView is focusable.
|
|
return true;
|
|
}
|
|
|
|
void STableViewBase::OnFocusLost( const FFocusEvent& InFocusEvent )
|
|
{
|
|
bShowSoftwareCursor = false;
|
|
}
|
|
|
|
void STableViewBase::OnMouseCaptureLost(const FCaptureLostEvent& CaptureLostEvent)
|
|
{
|
|
SCompoundWidget::OnMouseCaptureLost(CaptureLostEvent);
|
|
|
|
bShowSoftwareCursor = false;
|
|
}
|
|
|
|
EActiveTimerReturnType STableViewBase::UpdateInertialScroll(double InCurrentTime, float InDeltaTime)
|
|
{
|
|
bool bKeepTicking = false;
|
|
if (ItemsPanel.IsValid())
|
|
{
|
|
if (IsRightClickScrolling())
|
|
{
|
|
bKeepTicking = true;
|
|
|
|
// We sample for the inertial scroll on tick rather than on mouse/touch move so
|
|
// that we still get samples even if the mouse has not moved.
|
|
if (CanUseInertialScroll(TickScrollDelta))
|
|
{
|
|
InertialScrollManager.AddScrollSample(TickScrollDelta, InCurrentTime);
|
|
bIsInertialScroll = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const float CurrentSmoothDeltaTime = GetSmoothDeltaTime(InDeltaTime);
|
|
InertialScrollManager.UpdateScrollVelocity(CurrentSmoothDeltaTime);
|
|
const float ScrollVelocity = InertialScrollManager.GetScrollVelocity();
|
|
|
|
if (ScrollVelocity != 0.f)
|
|
{
|
|
if (CanUseInertialScroll(ScrollVelocity))
|
|
{
|
|
bKeepTicking = true;
|
|
ScrollBy(GetTickSpaceGeometry(), ScrollVelocity * CurrentSmoothDeltaTime, AllowOverscroll);
|
|
}
|
|
else
|
|
{
|
|
EndInertialScrolling();
|
|
}
|
|
}
|
|
|
|
if (AllowOverscroll == EAllowOverscroll::Yes)
|
|
{
|
|
// If we are currently in overscroll, the list will need refreshing.
|
|
// Do this before UpdateOverscroll, as that could cause GetOverscroll() to be 0
|
|
if (Overscroll.GetOverscroll(GetTickSpaceGeometry()) != 0.0f)
|
|
{
|
|
bKeepTicking = true;
|
|
RequestLayoutRefresh();
|
|
}
|
|
|
|
Overscroll.UpdateOverscroll(InDeltaTime);
|
|
}
|
|
}
|
|
|
|
TickScrollDelta = 0.f;
|
|
}
|
|
|
|
bIsScrollingActiveTimerRegistered = bKeepTicking;
|
|
return bKeepTicking ? EActiveTimerReturnType::Continue : EActiveTimerReturnType::Stop;
|
|
}
|
|
|
|
EActiveTimerReturnType STableViewBase::EnsureTickToRefresh(double InCurrentTime, float InDeltaTime)
|
|
{
|
|
// Actual refresh isn't implemented here as it can be needed in response to changes in the panel geometry.
|
|
// Since that isn't known until Tick (called after this when registered), refreshing here could result in two refreshes in one frame.
|
|
|
|
return EActiveTimerReturnType::Stop;
|
|
}
|
|
|
|
void STableViewBase::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime )
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(STableViewBase::Tick);
|
|
|
|
if (ItemsPanel.IsValid())
|
|
{
|
|
FGeometry PanelGeometry = FindChildGeometry( AllottedGeometry, ItemsPanel.ToSharedRef() );
|
|
|
|
bool bPanelGeometryChanged = PanelGeometryLastTick.GetLocalSize() != PanelGeometry.GetLocalSize();
|
|
|
|
if ( bItemsNeedRefresh || bPanelGeometryChanged)
|
|
{
|
|
UpdateSmoothDeltaTime(InDeltaTime);
|
|
|
|
PanelGeometryLastTick = PanelGeometry;
|
|
|
|
const int32 NumItemsPerLine = GetNumItemsPerLine();
|
|
const EScrollIntoViewResult ScrollIntoViewResult = ScrollIntoView(PanelGeometry);
|
|
|
|
double TargetScrollOffset = GetTargetScrollOffset();
|
|
|
|
if (InertialScrollManager.GetShouldStopScrollNow())
|
|
{
|
|
TargetScrollOffset = DesiredScrollOffset = CurrentScrollOffset;
|
|
InertialScrollManager.ResetShouldStopScrollNow();
|
|
}
|
|
|
|
bool bEndScroll = true;
|
|
if ((bStartedTouchInteraction && bEnableTouchAnimatedScrolling) || (!bStartedTouchInteraction && bEnableAnimatedScrolling))
|
|
{
|
|
CurrentScrollOffset = FMath::FInterpTo(CurrentScrollOffset, TargetScrollOffset, (double)GetSmoothDeltaTime(InDeltaTime), ScrollingAnimationInterpolationSpeed);
|
|
// We may need to wait until a new target scroll offset is recalculated at 0 velocity before we can end the scroll
|
|
const bool bWaitForZeroVelocityAutoScroll = ShouldScrollToFixedLineOffsetAtZeroVelocity() && InertialScrollManager.GetScrollVelocity() != 0.f;
|
|
bEndScroll = !bWaitForZeroVelocityAutoScroll && FMath::IsNearlyEqual(CurrentScrollOffset, TargetScrollOffset, 0.01);
|
|
}
|
|
|
|
if (bEndScroll)
|
|
{
|
|
CurrentScrollOffset = TargetScrollOffset;
|
|
}
|
|
|
|
const FReGenerateResults ReGenerateResults = ReGenerateItems( PanelGeometry );
|
|
LastGenerateResults = ReGenerateResults;
|
|
|
|
const int32 NumItemsBeingObserved = GetNumItemsBeingObserved();
|
|
const int32 NumItemLines = NumItemsBeingObserved / NumItemsPerLine;
|
|
|
|
const double InitialDesiredOffset = DesiredScrollOffset;
|
|
const bool bEnoughRoomForAllItems = ReGenerateResults.ExactNumLinesOnScreen >= NumItemLines;
|
|
if (bEnoughRoomForAllItems)
|
|
{
|
|
// We can show all the items, so make sure there is no scrolling.
|
|
SetScrollOffset(0.0);
|
|
CurrentScrollOffset = TargetScrollOffset = DesiredScrollOffset;
|
|
}
|
|
else if (ReGenerateResults.bGeneratedPastLastItem)
|
|
{
|
|
SetScrollOffset(FMath::Max(0.0, ReGenerateResults.NewScrollOffset));
|
|
CurrentScrollOffset = TargetScrollOffset = DesiredScrollOffset;
|
|
}
|
|
|
|
ItemsPanel->SetFirstLineScrollOffset(GetFirstLineScrollOffset());
|
|
|
|
if (AllowOverscroll == EAllowOverscroll::Yes)
|
|
{
|
|
const float OverscrollAmount = Overscroll.GetOverscroll(GetTickSpaceGeometry());
|
|
ItemsPanel->SetOverscrollAmount( OverscrollAmount );
|
|
}
|
|
|
|
UpdateSelectionSet();
|
|
|
|
// Update scrollbar
|
|
if (NumItemsBeingObserved > 0)
|
|
{
|
|
if (ReGenerateResults.ExactNumLinesOnScreen < 1.0f)
|
|
{
|
|
// We are observing a single row which is larger than the available visible area, so we should calculate thumb size based on that
|
|
const double VisibleSizeFraction = (Orientation == Orient_Vertical ? AllottedGeometry.GetLocalSize().Y : AllottedGeometry.GetLocalSize().X) / ReGenerateResults.LengthOfGeneratedItems / NumItemLines;
|
|
const double ThumbSizeFraction = FMath::Min(VisibleSizeFraction, 1.0);
|
|
const double OffsetFraction = CurrentScrollOffset / NumItemsBeingObserved;
|
|
ScrollBar->SetState( OffsetFraction, ThumbSizeFraction );
|
|
}
|
|
else
|
|
{
|
|
// The thumb size is whatever fraction of the items we are currently seeing (including partially seen items).
|
|
// e.g. if we are seeing 0.5 of the first generated widget and 0.75 of the last widget, that's 1.25 widgets.
|
|
const double ThumbSizeFraction = ReGenerateResults.ExactNumLinesOnScreen / NumItemLines;
|
|
const double OffsetFraction = CurrentScrollOffset / NumItemsBeingObserved;
|
|
ScrollBar->SetState( OffsetFraction, ThumbSizeFraction );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const double ThumbSizeFraction = 1;
|
|
const double OffsetFraction = 0;
|
|
ScrollBar->SetState( OffsetFraction, ThumbSizeFraction );
|
|
}
|
|
|
|
bWasAtEndOfList = (ScrollBar->DistanceFromBottom() < SMALL_NUMBER);
|
|
|
|
bItemsNeedRefresh = false;
|
|
ItemsPanel->SetRefreshPending(false);
|
|
|
|
Invalidate(EInvalidateWidget::ChildOrder);
|
|
|
|
if (ScrollIntoViewResult == EScrollIntoViewResult::Success)
|
|
{
|
|
// Notify as soon as we've made a widget for the item, even if we still have scrolling to do
|
|
NotifyItemScrolledIntoView();
|
|
}
|
|
if (ScrollIntoViewResult == EScrollIntoViewResult::Deferred || CurrentScrollOffset != TargetScrollOffset)
|
|
{
|
|
// Either we haven't made the item yet or we still have scrolling to do, so we'll need another refresh next frame
|
|
// We call this rather than just leave bItemsNeedRefresh as true to ensure that EnsureTickToRefresh is registered
|
|
RequestLayoutRefresh();
|
|
}
|
|
else if (CurrentScrollOffset == TargetScrollOffset
|
|
&& !IsUserScrolling()
|
|
&& (AllowOverscroll == EAllowOverscroll::No || Overscroll.GetOverscroll(GetTickSpaceGeometry()) == 0.0f))
|
|
{
|
|
// Sync the desired scroll offset with the target scroll offset when the scroll ends.
|
|
// This prevents teleporting the table upon the next scroll.
|
|
DesiredScrollOffset = TargetScrollOffset;
|
|
|
|
NotifyFinishedScrolling();
|
|
}
|
|
|
|
OnItemsRebuilt.ExecuteIfBound();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void STableViewBase::ScrollBar_OnUserScrolled( float InScrollOffsetFraction )
|
|
{
|
|
const double ClampedScrollOffsetInItems = FMath::Clamp<double>( InScrollOffsetFraction, 0.0, 1.0 )* GetNumItemsBeingObserved();
|
|
ScrollTo( ClampedScrollOffsetInItems );
|
|
}
|
|
|
|
|
|
FReply STableViewBase::OnPreviewMouseButtonDown( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
|
|
{
|
|
if (bEnableTouchScrolling && MouseEvent.IsTouchEvent())
|
|
{
|
|
// Clear any inertia
|
|
EndInertialScrolling(true);
|
|
// We have started a new interaction; track how far the user has moved since they put their finger down.
|
|
AmountScrolledWhileRightMouseDown = 0;
|
|
|
|
PressedScreenSpacePosition = MouseEvent.GetScreenSpacePosition();
|
|
|
|
// Someone put their finger down in this list, so they probably want to drag the list.
|
|
bStartedTouchInteraction = true;
|
|
return FReply::Unhandled();
|
|
}
|
|
else
|
|
{
|
|
return FReply::Unhandled();
|
|
}
|
|
}
|
|
|
|
|
|
FReply STableViewBase::OnMouseButtonDown( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
|
|
{
|
|
// Zero the scroll velocity so the list stops immediately on mouse down, even if the user does not drag
|
|
EndInertialScrolling(true);
|
|
|
|
if (MouseEvent.GetEffectingButton() == EKeys::RightMouseButton)
|
|
{
|
|
OnRightMouseButtonDown(MouseEvent);
|
|
}
|
|
|
|
if ( MouseEvent.GetEffectingButton() == EKeys::RightMouseButton && ScrollBar->IsNeeded() )
|
|
{
|
|
AmountScrolledWhileRightMouseDown = 0;
|
|
|
|
// NOTE: We don't bother capturing the mouse, unless the user starts dragging a few pixels (see the
|
|
// mouse move handling here.) This is important so that the item row has a chance to select
|
|
// items when the right mouse button is released. Just keep in mind that you might not get
|
|
// an OnMouseButtonUp event for the right mouse button if the user moves off of the table before
|
|
// they reach our scroll threshold
|
|
return FReply::Handled();
|
|
}
|
|
else if ( this->HasMouseCapture() )
|
|
{
|
|
// Consume all mouse buttons while we are RMB-dragging.
|
|
return FReply::Handled();
|
|
}
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
FReply STableViewBase::OnMouseButtonDoubleClick( const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent )
|
|
{
|
|
if ( this->HasMouseCapture() )
|
|
{
|
|
// Consume all other mouse buttons while we are RMB-dragging.
|
|
return FReply::Handled();
|
|
}
|
|
return FReply::Unhandled();
|
|
|
|
}
|
|
|
|
|
|
FReply STableViewBase::OnMouseButtonUp( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
|
|
{
|
|
if ( MouseEvent.GetEffectingButton() == EKeys::RightMouseButton )
|
|
{
|
|
|
|
OnRightMouseButtonUp( MouseEvent );
|
|
|
|
FReply Reply = FReply::Handled().ReleaseMouseCapture();
|
|
bShowSoftwareCursor = false;
|
|
|
|
// If we have mouse capture, snap the mouse back to the closest location that is within the list's bounds
|
|
if ( HasMouseCapture() )
|
|
{
|
|
FSlateRect ListScreenSpaceRect = MyGeometry.GetLayoutBoundingRect();
|
|
FVector2f CursorPosition = MyGeometry.LocalToAbsolute( SoftwareCursorPosition );
|
|
|
|
FIntPoint BestPositionInList(
|
|
FMath::RoundToInt( FMath::Clamp( CursorPosition.X, ListScreenSpaceRect.Left, ListScreenSpaceRect.Right ) ),
|
|
FMath::RoundToInt( FMath::Clamp( CursorPosition.Y, ListScreenSpaceRect.Top, ListScreenSpaceRect.Bottom ) )
|
|
);
|
|
|
|
Reply.SetMousePos(BestPositionInList);
|
|
}
|
|
|
|
return Reply;
|
|
}
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
|
|
FReply STableViewBase::OnMouseMove( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
|
|
{
|
|
if( bEnableRightClickScrolling && MouseEvent.IsMouseButtonDown( EKeys::RightMouseButton ) && !MouseEvent.IsTouchEvent() && bIsPointerScrollingEnabled )
|
|
{
|
|
// We only care about deltas along the scroll axis
|
|
FTableViewDimensions CursorDeltaDimensions(Orientation, MouseEvent.GetCursorDelta());
|
|
CursorDeltaDimensions.LineAxis = 0.f;
|
|
|
|
const float ScrollByAmount = CursorDeltaDimensions.ScrollAxis / MyGeometry.Scale;
|
|
|
|
// If scrolling with the right mouse button, we need to remember how much we scrolled.
|
|
// If we did not scroll at all, we will bring up the context menu when the mouse is released.
|
|
AmountScrolledWhileRightMouseDown += FMath::Abs( ScrollByAmount );
|
|
|
|
// Has the mouse moved far enough with the right mouse button held down to start capturing
|
|
// the mouse and dragging the view?
|
|
if( IsRightClickScrolling() )
|
|
{
|
|
// Make sure the active timer is registered to update the inertial scroll
|
|
if (!bIsScrollingActiveTimerRegistered)
|
|
{
|
|
bIsScrollingActiveTimerRegistered = true;
|
|
RegisterActiveTimer(0.f, FWidgetActiveTimerDelegate::CreateSP(this, &STableViewBase::UpdateInertialScroll));
|
|
}
|
|
|
|
TickScrollDelta -= ScrollByAmount;
|
|
|
|
const float AmountScrolled = this->ScrollBy( MyGeometry, -ScrollByAmount, AllowOverscroll );
|
|
|
|
FReply Reply = FReply::Handled();
|
|
|
|
// The mouse moved enough that we're now dragging the view. Capture the mouse
|
|
// so the user does not have to stay within the bounds of the list while dragging.
|
|
if(this->HasMouseCapture() == false)
|
|
{
|
|
Reply.CaptureMouse( AsShared() ).UseHighPrecisionMouseMovement( AsShared() );
|
|
SoftwareCursorPosition = MyGeometry.AbsoluteToLocal( MouseEvent.GetScreenSpacePosition() );
|
|
bShowSoftwareCursor = true;
|
|
}
|
|
|
|
// Check if the mouse has moved.
|
|
if( AmountScrolled != 0 )
|
|
{
|
|
SoftwareCursorPosition += CursorDeltaDimensions.ToVector2D();
|
|
}
|
|
|
|
return Reply;
|
|
}
|
|
}
|
|
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
|
|
void STableViewBase::OnMouseEnter( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
|
|
{
|
|
if (bEnableTouchScrolling && MouseEvent.IsTouchEvent())
|
|
{
|
|
if ( !bStartedTouchInteraction )
|
|
{
|
|
// If we don't have touch capture, see if a touch event entered from a child widget.
|
|
// If it did, begin scrolling
|
|
if ( MyGeometry.IsUnderLocation(MouseEvent.GetLastScreenSpacePosition()) )
|
|
{
|
|
bStartedTouchInteraction = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void STableViewBase::OnMouseLeave( const FPointerEvent& MouseEvent )
|
|
{
|
|
SCompoundWidget::OnMouseLeave(MouseEvent);
|
|
|
|
bStartedTouchInteraction = false;
|
|
if(this->HasMouseCapture() == false)
|
|
{
|
|
// No longer scrolling (unless we have mouse capture)
|
|
AmountScrolledWhileRightMouseDown = 0;
|
|
}
|
|
}
|
|
|
|
|
|
FReply STableViewBase::OnMouseWheel( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
|
|
{
|
|
if (WheelScrollMultiplier == 0.0f)
|
|
{
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
if( bIsPointerScrollingEnabled && !MouseEvent.IsControlDown() )
|
|
{
|
|
// Make sure scroll velocity is cleared so it doesn't fight with the mouse wheel input
|
|
EndInertialScrolling();
|
|
|
|
float AmountScrolledInItems = 0.f;
|
|
if (FixedLineScrollOffset.IsSet())
|
|
{
|
|
// When we need to maintain a fixed offset, we scroll by items. This prevents the list not moving or jumping unexpectedly far on an individual scroll wheel motion.
|
|
const double AdditionalOffset = (MouseEvent.GetWheelDelta() >= 0.f ? -1.f : 1.f) * GetNumItemsPerLine();
|
|
const double NewScrollOffset = FMath::Max(0., DesiredScrollOffset + AdditionalOffset);
|
|
AmountScrolledInItems = this->ScrollTo(NewScrollOffset);
|
|
}
|
|
else
|
|
{
|
|
// No required offset to maintain, so we scroll by units
|
|
AmountScrolledInItems = this->ScrollBy(MyGeometry, -MouseEvent.GetWheelDelta() * WheelScrollMultiplier, EAllowOverscroll::No);
|
|
}
|
|
|
|
if (ConsumeMouseWheel == EConsumeMouseWheel::Always || (FMath::Abs(AmountScrolledInItems) > 0.0f && ConsumeMouseWheel != EConsumeMouseWheel::Never))
|
|
{
|
|
return FReply::Handled();
|
|
}
|
|
}
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
|
|
FReply STableViewBase::OnKeyDown( const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent )
|
|
{
|
|
if ( InKeyEvent.IsControlDown() && InKeyEvent.GetKey() == EKeys::End )
|
|
{
|
|
ScrollToBottom();
|
|
return FReply::Handled();
|
|
}
|
|
return SCompoundWidget::OnKeyDown(MyGeometry, InKeyEvent);
|
|
}
|
|
|
|
FCursorReply STableViewBase::OnCursorQuery( const FGeometry& MyGeometry, const FPointerEvent& CursorEvent ) const
|
|
{
|
|
if ( IsRightClickScrolling() && CursorEvent.IsMouseButtonDown(EKeys::RightMouseButton) )
|
|
{
|
|
// We hide the native cursor as we'll be drawing the software EMouseCursor::GrabHandClosed cursor
|
|
return FCursorReply::Cursor( EMouseCursor::None );
|
|
}
|
|
else
|
|
{
|
|
return FCursorReply::Unhandled();
|
|
}
|
|
}
|
|
|
|
FReply STableViewBase::OnTouchStarted( const FGeometry& MyGeometry, const FPointerEvent& InTouchEvent )
|
|
{
|
|
// See OnPreviewMouseButtonDown()
|
|
// if (MouseEvent.IsTouchEvent())
|
|
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
FReply STableViewBase::OnTouchMoved( const FGeometry& MyGeometry, const FPointerEvent& InTouchEvent )
|
|
{
|
|
if (bIsPointerScrollingEnabled && bEnableTouchScrolling && bStartedTouchInteraction)
|
|
{
|
|
// We only care about deltas along the scroll axis
|
|
FTableViewDimensions CursorDeltaDimensions(Orientation, InTouchEvent.GetCursorDelta());
|
|
CursorDeltaDimensions.LineAxis = 0.f;
|
|
|
|
const float ScrollByAmount = CursorDeltaDimensions.ScrollAxis / MyGeometry.Scale;
|
|
|
|
AmountScrolledWhileRightMouseDown += FMath::Abs( ScrollByAmount );
|
|
|
|
TickScrollDelta -= ScrollByAmount;
|
|
|
|
if (FSlateApplication::Get().HasTraveledFarEnoughToTriggerDrag(InTouchEvent, PressedScreenSpacePosition, Orientation))
|
|
{
|
|
// Make sure the active timer is registered to update the inertial scroll
|
|
if ( !bIsScrollingActiveTimerRegistered )
|
|
{
|
|
bIsScrollingActiveTimerRegistered = true;
|
|
RegisterActiveTimer(0.f, FWidgetActiveTimerDelegate::CreateSP(this, &STableViewBase::UpdateInertialScroll));
|
|
}
|
|
|
|
const float AmountScrolled = this->ScrollBy( MyGeometry, -ScrollByAmount, AllowOverscroll );
|
|
if (AmountScrolled != 0)
|
|
{
|
|
ScrollBar->BeginScrolling();
|
|
|
|
// The user has moved the list some amount; they are probably
|
|
// trying to scroll. From now on, the list assumes the user is scrolling
|
|
// until they lift their finger.
|
|
return HasMouseCapture() ? FReply::Handled() : FReply::Handled().CaptureMouse(AsShared());
|
|
}
|
|
}
|
|
|
|
return FReply::Unhandled();
|
|
}
|
|
else
|
|
{
|
|
return FReply::Unhandled();
|
|
}
|
|
}
|
|
|
|
FReply STableViewBase::OnTouchEnded( const FGeometry& MyGeometry, const FPointerEvent& InTouchEvent )
|
|
{
|
|
AmountScrolledWhileRightMouseDown = 0;
|
|
bStartedTouchInteraction = false;
|
|
|
|
ScrollBar->EndScrolling();
|
|
|
|
if (HasMouseCapture())
|
|
{
|
|
return FReply::Handled().ReleaseMouseCapture();
|
|
}
|
|
else
|
|
{
|
|
return FReply::Unhandled();
|
|
}
|
|
}
|
|
|
|
|
|
int32 STableViewBase::GetNumGeneratedChildren() const
|
|
{
|
|
return (ItemsPanel.IsValid())
|
|
? ItemsPanel->GetChildren()->Num()
|
|
: 0;
|
|
}
|
|
|
|
TSharedPtr<SWidget> STableViewBase::GetGeneratedChildAt(const int32 Index) const
|
|
{
|
|
return GetNumGeneratedChildren() > Index ? ItemsPanel->GetChildren()->GetChildAt(Index).ToSharedPtr() : nullptr;
|
|
}
|
|
|
|
TSharedPtr<SHeaderRow> STableViewBase::GetHeaderRow() const
|
|
{
|
|
return HeaderRow;
|
|
}
|
|
|
|
bool STableViewBase::IsRightClickScrolling() const
|
|
{
|
|
return AmountScrolledWhileRightMouseDown >= FSlateApplication::Get().GetDragTriggerDistance() &&
|
|
(this->ScrollBar->IsNeeded() || AllowOverscroll == EAllowOverscroll::Yes);
|
|
}
|
|
|
|
bool STableViewBase::IsUserScrolling() const
|
|
{
|
|
bool bUserScroll = this->ScrollBar->IsNeeded() && this->ScrollBar->IsScrolling();
|
|
return bUserScroll || IsRightClickScrolling();
|
|
}
|
|
|
|
void STableViewBase::RequestListRefresh()
|
|
{
|
|
RequestLayoutRefresh();
|
|
}
|
|
|
|
bool STableViewBase::IsPendingRefresh() const
|
|
{
|
|
return bItemsNeedRefresh || ItemsPanel->IsRefreshPending();
|
|
}
|
|
|
|
bool STableViewBase::ComputeVolatility() const
|
|
{
|
|
return BackgroundBrush.IsBound();
|
|
}
|
|
|
|
int32 STableViewBase::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const
|
|
{
|
|
int32 NewLayerId = LayerId;
|
|
|
|
const FSlateBrush* BackgroundBrushResource = BackgroundBrush.Get();
|
|
if ( BackgroundBrushResource && BackgroundBrushResource->DrawAs != ESlateBrushDrawType::NoDrawType )
|
|
{
|
|
|
|
const bool bIsEnabled = ShouldBeEnabled(bParentEnabled);
|
|
const ESlateDrawEffect DrawEffects = bIsEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect;
|
|
|
|
FSlateDrawElement::MakeBox(
|
|
OutDrawElements,
|
|
++NewLayerId,
|
|
AllottedGeometry.ToPaintGeometry(),
|
|
BackgroundBrushResource,
|
|
DrawEffects,
|
|
BackgroundBrushResource->GetTint(InWidgetStyle) * InWidgetStyle.GetColorAndOpacityTint()
|
|
);
|
|
}
|
|
|
|
NewLayerId = SCompoundWidget::OnPaint( Args, AllottedGeometry, MyCullingRect, OutDrawElements, NewLayerId, InWidgetStyle, bParentEnabled );
|
|
|
|
if( !bShowSoftwareCursor )
|
|
{
|
|
return NewLayerId;
|
|
}
|
|
|
|
const FSlateBrush* Brush = FCoreStyle::Get().GetBrush(TEXT("SoftwareCursor_Grab"));
|
|
const FVector2f CursorSize = Brush->ImageSize / AllottedGeometry.Scale;
|
|
|
|
FSlateDrawElement::MakeBox(
|
|
OutDrawElements,
|
|
++NewLayerId,
|
|
AllottedGeometry.ToPaintGeometry(CursorSize, FSlateLayoutTransform(SoftwareCursorPosition - (CursorSize / .5f ))),
|
|
Brush
|
|
);
|
|
|
|
return NewLayerId;
|
|
}
|
|
|
|
STableViewBase::STableViewBase( ETableViewMode::Type InTableViewMode )
|
|
: TableViewMode( InTableViewMode )
|
|
, bStartedTouchInteraction( false )
|
|
, AmountScrolledWhileRightMouseDown( 0 )
|
|
, TickScrollDelta( 0 )
|
|
, LastGenerateResults( 0,0,0,false )
|
|
, bWasAtEndOfList(false)
|
|
, SelectionMode( ESelectionMode::Multi )
|
|
, SoftwareCursorPosition( ForceInitToZero )
|
|
, bShowSoftwareCursor( false )
|
|
, WheelScrollMultiplier(GetGlobalScrollAmount())
|
|
, bShouldUseShadowBoxStyle(false)
|
|
, ShadowBoxStyle( &FAppStyle::Get().GetWidgetStyle<FScrollBoxStyle>("ScrollBox"))
|
|
, BackgroundBrush(FStyleDefaults::GetNoBrush())
|
|
, bIsScrollingActiveTimerRegistered( false )
|
|
, Overscroll()
|
|
, AllowOverscroll(EAllowOverscroll::Yes)
|
|
, ConsumeMouseWheel(EConsumeMouseWheel::WhenScrollingPossible)
|
|
, bItemsNeedRefresh( false )
|
|
{
|
|
FixedLineScrollOffset = 0.25f;
|
|
}
|
|
|
|
STableViewBase::~STableViewBase() = default;
|
|
|
|
double STableViewBase::GetTargetScrollOffset() const
|
|
{
|
|
if (ShouldScrollToFixedLineOffsetAtZeroVelocity() && InertialScrollManager.GetScrollVelocity() == 0.f)
|
|
{
|
|
// The scroll has ended, we calculate the appropriate the scroll offset to snap on a line.
|
|
// We don't do that if we are at the end of the list because in this case showing the last line correctly is more important.
|
|
const int32 NumItemsPerLine = GetNumItemsPerLine();
|
|
double DesiredLineOffset = DesiredScrollOffset / NumItemsPerLine;
|
|
const double RoundedDesiredLineOffset = FMath::RoundToDouble(DesiredLineOffset);
|
|
|
|
// If DesiredLineOffset is almost equal to RoundedDesiredLineOffset:
|
|
// There is no need to round it and it is better not to floor it in case it is slightly less that the rounded value
|
|
if (!FMath::IsNearlyEqual(RoundedDesiredLineOffset, DesiredLineOffset, UE_DOUBLE_KINDA_SMALL_NUMBER))
|
|
{
|
|
if (bIsInertialScroll)
|
|
{
|
|
// Scroll to the item most in view
|
|
DesiredLineOffset = RoundedDesiredLineOffset;
|
|
}
|
|
else
|
|
{
|
|
// Scroll to the topmost item.
|
|
DesiredLineOffset = FMath::FloorToDouble(DesiredLineOffset);
|
|
}
|
|
}
|
|
DesiredLineOffset -= FixedLineScrollOffset.GetValue();
|
|
|
|
return FMath::Max(0.0, DesiredLineOffset * NumItemsPerLine);
|
|
}
|
|
return DesiredScrollOffset;
|
|
}
|
|
|
|
bool STableViewBase::ShouldScrollToFixedLineOffsetAtZeroVelocity() const
|
|
{
|
|
return !bWasAtEndOfList && FixedLineScrollOffset.IsSet() && !IsRightClickScrolling();
|
|
}
|
|
|
|
float STableViewBase::GetSmoothDeltaTime(float InDeltaTime)
|
|
{
|
|
// This is to be sure we have an initial value.
|
|
return SmoothDeltaTime >= 0 ? SmoothDeltaTime : InDeltaTime;
|
|
}
|
|
|
|
void STableViewBase::UpdateSmoothDeltaTime(float InDeltaTime)
|
|
{
|
|
if (DeltaTimeQueue.IsValidIndex(DeltaTimeCircularIndex))
|
|
{
|
|
DeltaTimeQueue[DeltaTimeCircularIndex] = InDeltaTime;
|
|
}
|
|
else
|
|
{
|
|
DeltaTimeQueue.Add(InDeltaTime);
|
|
}
|
|
DeltaTimeCircularIndex = (DeltaTimeCircularIndex + 1) % MaxNumberOfDeltaTimes;
|
|
|
|
SortedDeltaTimes.Empty(MaxNumberOfDeltaTimes);
|
|
SortedDeltaTimes.Append(DeltaTimeQueue);
|
|
SortedDeltaTimes.Sort();
|
|
|
|
float SumOfDeltaTimes = 0.0f;
|
|
|
|
const int32 NumberOfOutliers = FMath::RoundFromZero(SortedDeltaTimes.Num() * FMath::Abs(RatioOfOutliers));
|
|
const int32 HalfNumberOfOutliers = NumberOfOutliers / 2;
|
|
|
|
const int32 StartIndex = HalfNumberOfOutliers;
|
|
const int32 EndIndex = SortedDeltaTimes.Num() - HalfNumberOfOutliers;
|
|
|
|
ensure(EndIndex - StartIndex > 0);
|
|
|
|
for (int32 i = StartIndex; i < EndIndex; ++i)
|
|
{
|
|
SumOfDeltaTimes += SortedDeltaTimes[i];
|
|
}
|
|
|
|
SmoothDeltaTime = SumOfDeltaTimes / (EndIndex - StartIndex);
|
|
}
|
|
|
|
float STableViewBase::ScrollBy(const FGeometry& MyGeometry, float ScrollByAmountInSlateUnits, EAllowOverscroll InAllowOverscroll)
|
|
{
|
|
const int32 NumItemsBeingObserved = GetNumItemsBeingObserved();
|
|
const float FractionalScrollOffsetInItems = (DesiredScrollOffset + GetScrollRateInItems() * ScrollByAmountInSlateUnits) / NumItemsBeingObserved;
|
|
const double ClampedScrollOffsetInItems = FMath::Clamp<double>( FractionalScrollOffsetInItems*NumItemsBeingObserved, -10.0f, NumItemsBeingObserved+10.0f ) * NumItemsBeingObserved;
|
|
if (InAllowOverscroll == EAllowOverscroll::Yes)
|
|
{
|
|
Overscroll.ScrollBy(MyGeometry, ClampedScrollOffsetInItems - ScrollByAmountInSlateUnits );
|
|
}
|
|
return ScrollTo( ClampedScrollOffsetInItems );
|
|
}
|
|
|
|
float STableViewBase::ScrollTo( float InScrollOffset)
|
|
{
|
|
const float NewScrollOffset = FMath::Clamp( InScrollOffset, -10.0f, GetNumItemsBeingObserved()+10.0f );
|
|
float AmountScrolled = FMath::Abs( DesiredScrollOffset - NewScrollOffset );
|
|
|
|
if (bWasAtEndOfList && NewScrollOffset >= DesiredScrollOffset)
|
|
{
|
|
AmountScrolled = 0;
|
|
}
|
|
|
|
SetScrollOffset( NewScrollOffset );
|
|
|
|
return AmountScrolled;
|
|
}
|
|
|
|
float STableViewBase::GetScrollOffset() const
|
|
{
|
|
return DesiredScrollOffset;
|
|
}
|
|
|
|
void STableViewBase::SetScrollOffset( const float InScrollOffset )
|
|
{
|
|
const float InValidatedOffset = FMath::Max(0.0f, InScrollOffset);
|
|
if (DesiredScrollOffset != InValidatedOffset)
|
|
{
|
|
DesiredScrollOffset = InValidatedOffset;
|
|
OnTableViewScrolled.ExecuteIfBound(DesiredScrollOffset);
|
|
RequestLayoutRefresh();
|
|
}
|
|
}
|
|
|
|
void STableViewBase::EndInertialScrolling(const bool bInShouldStopScrollNow)
|
|
{
|
|
InertialScrollManager.ClearScrollVelocity(bInShouldStopScrollNow);
|
|
bIsInertialScroll = false;
|
|
}
|
|
|
|
void STableViewBase::AddScrollOffset(const float InScrollOffsetDelta, bool RefreshList)
|
|
{
|
|
if (FMath::IsNearlyEqual(InScrollOffsetDelta, 0.0f) == false)
|
|
{
|
|
DesiredScrollOffset += InScrollOffsetDelta;
|
|
if (RefreshList)
|
|
{
|
|
OnTableViewScrolled.ExecuteIfBound(DesiredScrollOffset);
|
|
RequestLayoutRefresh();
|
|
}
|
|
}
|
|
}
|
|
|
|
void STableViewBase::SetScrollbarVisibility(const EVisibility InVisibility)
|
|
{
|
|
if (ScrollBar)
|
|
{
|
|
ScrollBar->SetVisibility(InVisibility);
|
|
}
|
|
}
|
|
|
|
void STableViewBase::SetScrollbarPadding(const FMargin& InScrollbarPadding)
|
|
{
|
|
ScrollBarSlotPadding = InScrollbarPadding;
|
|
|
|
if (Orientation == Orient_Vertical)
|
|
{
|
|
if (VerticalScrollBarSlot)
|
|
{
|
|
VerticalScrollBarSlot->SetPadding(ScrollBarSlotPadding);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (HorizontalScrollBarSlot)
|
|
{
|
|
HorizontalScrollBarSlot->SetPadding(ScrollBarSlotPadding);
|
|
}
|
|
}
|
|
}
|
|
|
|
EVisibility STableViewBase::GetScrollbarVisibility() const
|
|
{
|
|
return ScrollBar ? ScrollBar->ShouldBeVisible() : EVisibility::Collapsed;
|
|
}
|
|
|
|
bool STableViewBase::IsScrollbarNeeded() const
|
|
{
|
|
if (ScrollBar)
|
|
{
|
|
return ScrollBar->IsNeeded();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void STableViewBase::SetFixedLineScrollOffset(TOptional<double> InFixedLineScrollOffset)
|
|
{
|
|
if (FixedLineScrollOffset != InFixedLineScrollOffset)
|
|
{
|
|
FixedLineScrollOffset = InFixedLineScrollOffset;
|
|
RequestLayoutRefresh();
|
|
}
|
|
}
|
|
|
|
void STableViewBase::SetIsScrollAnimationEnabled(bool bInEnableScrollAnimation)
|
|
{
|
|
bEnableAnimatedScrolling = bInEnableScrollAnimation;
|
|
}
|
|
|
|
void STableViewBase::SetScrollingAnimationInterpolationSpeed(float InScrollingAnimationInterpolationSpeed)
|
|
{
|
|
ScrollingAnimationInterpolationSpeed = InScrollingAnimationInterpolationSpeed;
|
|
}
|
|
|
|
void STableViewBase::SetEnableTouchAnimatedScrolling(bool bInEnableTouchAnimatedScrolling)
|
|
{
|
|
bEnableTouchAnimatedScrolling = bInEnableTouchAnimatedScrolling;
|
|
}
|
|
|
|
void STableViewBase::SetAllowOverscroll(EAllowOverscroll InAllowOverscroll)
|
|
{
|
|
AllowOverscroll = InAllowOverscroll;
|
|
}
|
|
|
|
void STableViewBase::SetIsRightClickScrollingEnabled(const bool bInEnableRightClickScrolling)
|
|
{
|
|
bEnableRightClickScrolling = bInEnableRightClickScrolling;
|
|
}
|
|
|
|
void STableViewBase::SetIsTouchScrollingEnabled(const bool bInEnableTouchScrolling)
|
|
{
|
|
bEnableTouchScrolling = bInEnableTouchScrolling;
|
|
ensureMsgf(!bStartedTouchInteraction, TEXT("TouchScrollingEnabled flag should not be changed while scrolling."));
|
|
}
|
|
|
|
void STableViewBase::SetSelectItemOnNavigation(const bool bInSelectItemOnNavigation)
|
|
{
|
|
bSelectItemOnNavigation = bInSelectItemOnNavigation;
|
|
}
|
|
|
|
void STableViewBase::SetWheelScrollMultiplier(float NewWheelScrollMultiplier)
|
|
{
|
|
WheelScrollMultiplier = NewWheelScrollMultiplier;
|
|
}
|
|
|
|
void STableViewBase::SetIsPointerScrollingEnabled(bool bInIsPointerScrollingEnabled)
|
|
{
|
|
bIsPointerScrollingEnabled = bInIsPointerScrollingEnabled;
|
|
}
|
|
|
|
void STableViewBase::SetIsGamepadScrollingEnabled(bool bInIsGamepadScrollingEnabled)
|
|
{
|
|
bIsGamepadScrollingEnabled = bInIsGamepadScrollingEnabled;
|
|
}
|
|
|
|
void STableViewBase::SetBackgroundBrush(const TAttribute<const FSlateBrush*>& InBackgroundBrush)
|
|
{
|
|
BackgroundBrush.SetImage(*this, InBackgroundBrush);
|
|
}
|
|
|
|
void STableViewBase::InsertWidget( const TSharedRef<ITableRow> & WidgetToInset )
|
|
{
|
|
ItemsPanel->AddSlot(0)
|
|
[
|
|
WidgetToInset->AsWidget()
|
|
];
|
|
}
|
|
|
|
void STableViewBase::AppendWidget( const TSharedRef<ITableRow>& WidgetToAppend )
|
|
{
|
|
ItemsPanel->AddSlot()
|
|
[
|
|
WidgetToAppend->AsWidget()
|
|
];
|
|
}
|
|
|
|
void STableViewBase::ClearWidgets()
|
|
{
|
|
ItemsPanel->ClearItems();
|
|
}
|
|
|
|
const FChildren* STableViewBase::GetConstructedTableItems() const
|
|
{
|
|
return ItemsPanel->GetChildren();
|
|
}
|
|
|
|
void STableViewBase::InsertPinnedWidget( const TSharedRef<SWidget> & WidgetToInset )
|
|
{
|
|
PinnedItemsPanel->AddSlot(0)
|
|
[
|
|
WidgetToInset
|
|
];
|
|
PinnedItemsPanel->SetVisibility(EVisibility::Visible);
|
|
}
|
|
|
|
void STableViewBase::AppendPinnedWidget( const TSharedRef<SWidget>& WidgetToAppend )
|
|
{
|
|
PinnedItemsPanel->AddSlot()
|
|
[
|
|
WidgetToAppend
|
|
];
|
|
PinnedItemsPanel->SetVisibility(EVisibility::Visible);
|
|
}
|
|
|
|
void STableViewBase::ClearPinnedWidgets()
|
|
{
|
|
PinnedItemsPanel->SetVisibility(EVisibility::Collapsed);
|
|
PinnedItemsPanel->ClearItems();
|
|
}
|
|
|
|
float STableViewBase::GetItemWidth() const
|
|
{
|
|
return GetItemSize().X;
|
|
}
|
|
|
|
float STableViewBase::GetItemHeight() const
|
|
{
|
|
return GetItemSize().Y;
|
|
}
|
|
|
|
UE::Slate::FDeprecateVector2DResult STableViewBase::GetItemSize() const
|
|
{
|
|
FTableViewDimensions ItemDimensions = ItemsPanel->GetItemSize(PanelGeometryLastTick);
|
|
ItemDimensions.LineAxis += ItemsPanel->GetItemPadding(PanelGeometryLastTick);
|
|
return ItemDimensions.ToVector2D();
|
|
}
|
|
|
|
void STableViewBase::SetItemHeight(TAttribute<float> Height)
|
|
{
|
|
ItemsPanel->SetItemHeight(Height);
|
|
}
|
|
|
|
void STableViewBase::SetItemWidth(TAttribute<float> Width)
|
|
{
|
|
ItemsPanel->SetItemWidth(Width);
|
|
}
|
|
|
|
float STableViewBase::GetNumLiveWidgets() const
|
|
{
|
|
return ItemsPanel->GetChildren()->Num();
|
|
}
|
|
|
|
int32 STableViewBase::GetNumItemsPerLine() const
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
float STableViewBase::GetFirstLineScrollOffset() const
|
|
{
|
|
// FMath::Fractional() is insufficient here as it casts to int32 (too small for the integer part of a float when
|
|
// the scroll offset is enormous), so we do a double/int64 version here.
|
|
const double FirstLineScrollOffset = CurrentScrollOffset / GetNumItemsPerLine();
|
|
return FirstLineScrollOffset - (int64)FirstLineScrollOffset;
|
|
}
|
|
|
|
void STableViewBase::NavigateToWidget(const uint32 UserIndex, const TSharedPtr<SWidget>& NavigationDestination, ENavigationSource NavigationSource, EUINavigation NavigationType) const
|
|
{
|
|
FSlateApplication::Get().NavigateToWidget(UserIndex, NavigationDestination, NavigationSource, NavigationType);
|
|
}
|
|
|
|
int32 STableViewBase::FindChildUnderPosition(FArrangedChildren& ArrangedChildren, const FVector2D& ArrangedSpacePosition) const
|
|
{
|
|
if (ItemsPanel.IsValid())
|
|
{
|
|
const FGeometry MyGeometry = ItemsPanel->GetCachedGeometry();
|
|
ItemsPanel->ArrangeChildren(MyGeometry, ArrangedChildren, true);
|
|
|
|
return ItemsPanel->FindChildUnderPosition(ArrangedChildren, ArrangedSpacePosition);
|
|
}
|
|
|
|
return INDEX_NONE;
|
|
}
|
|
|
|
void STableViewBase::OnRightMouseButtonUp(const FPointerEvent& MouseEvent)
|
|
{
|
|
FVector2f SummonLocation = MouseEvent.GetScreenSpacePosition();
|
|
const bool bShouldOpenContextMenu = !IsRightClickScrolling();
|
|
const bool bContextMenuOpeningBound = OnContextMenuOpening.IsBound();
|
|
|
|
if (bShouldOpenContextMenu && bContextMenuOpeningBound)
|
|
{
|
|
// Get the context menu content. If NULL, don't open a menu.
|
|
TSharedPtr<SWidget> MenuContent = OnContextMenuOpening.Execute();
|
|
|
|
if (MenuContent.IsValid())
|
|
{
|
|
bShowSoftwareCursor = false;
|
|
|
|
FWidgetPath WidgetPath = MouseEvent.GetEventPath() != nullptr ? *MouseEvent.GetEventPath() : FWidgetPath();
|
|
FSlateApplication::Get().PushMenu(AsShared(), WidgetPath, MenuContent.ToSharedRef(), SummonLocation, FPopupTransitionEffect(FPopupTransitionEffect::ContextMenu));
|
|
}
|
|
}
|
|
|
|
AmountScrolledWhileRightMouseDown = 0;
|
|
}
|
|
|
|
float STableViewBase::GetScrollRateInItems() const
|
|
{
|
|
return (LastGenerateResults.LengthOfGeneratedItems != 0 && LastGenerateResults.ExactNumLinesOnScreen != 0)
|
|
// Approximate a consistent scrolling rate based on the average item height.
|
|
? LastGenerateResults.ExactNumLinesOnScreen / LastGenerateResults.LengthOfGeneratedItems
|
|
// Scroll 1/2 an item at a time as a default.
|
|
: 0.5f;
|
|
}
|
|
|
|
void STableViewBase::RequestLayoutRefresh()
|
|
{
|
|
if (!bItemsNeedRefresh)
|
|
{
|
|
bItemsNeedRefresh = true;
|
|
RegisterActiveTimer(0.f, FWidgetActiveTimerDelegate::CreateSP(this, &STableViewBase::EnsureTickToRefresh));
|
|
}
|
|
|
|
if (ItemsPanel.IsValid())
|
|
{
|
|
ItemsPanel->SetRefreshPending(true);
|
|
}
|
|
|
|
Invalidate(EInvalidateWidget::Layout);
|
|
}
|
|
|
|
void STableViewBase::ScrollToTop()
|
|
{
|
|
EndInertialScrolling();
|
|
SetScrollOffset(0);
|
|
RequestLayoutRefresh();
|
|
}
|
|
|
|
void STableViewBase::ScrollToBottom()
|
|
{
|
|
EndInertialScrolling();
|
|
SetScrollOffset(GetNumItemsBeingObserved());
|
|
RequestLayoutRefresh();
|
|
}
|
|
|
|
bool STableViewBase::IsScrolling() const
|
|
{
|
|
return ScrollBar->IsScrolling();
|
|
}
|
|
|
|
FVector2D STableViewBase::GetScrollDistance()
|
|
{
|
|
return FVector2D( 0, ScrollBar->DistanceFromTop() );
|
|
}
|
|
|
|
FVector2D STableViewBase::GetScrollDistanceRemaining()
|
|
{
|
|
return FVector2D( 0, ScrollBar->DistanceFromBottom() );
|
|
}
|
|
|
|
TSharedRef<class SWidget> STableViewBase::GetScrollWidget()
|
|
{
|
|
return SharedThis(this);
|
|
}
|
|
|
|
void STableViewBase::OnClippingChanged()
|
|
{
|
|
ItemsPanel->SetClipping(GetClipping());
|
|
}
|
|
|
|
FSlateColor STableViewBase::GetStartShadowOpacity() const
|
|
{
|
|
// The shadow should only be visible when the user needs a hint that they can scroll up.
|
|
const float ShadowOpacity = FMath::Clamp( CurrentScrollOffset/ShadowFadeDistance, 0.0f, 1.0f);
|
|
|
|
return FLinearColor(1.0f, 1.0f, 1.0f, ShadowOpacity);
|
|
}
|
|
|
|
FSlateColor STableViewBase::GetEndShadowOpacity() const
|
|
{
|
|
// The shadow should only be visible when the user needs a hint that they can scroll down.
|
|
const float ShadowOpacity = (ScrollBar->DistanceFromBottom() * GetScrollComponentFromVector(ItemsPanel->GetDesiredSize()) / ShadowFadeDistance);
|
|
|
|
return FLinearColor(1.0f, 1.0f, 1.0f, ShadowOpacity);
|
|
}
|
|
|
|
bool STableViewBase::CanUseInertialScroll( float ScrollAmount ) const
|
|
{
|
|
const auto CurrentOverscroll = Overscroll.GetOverscroll(GetTickSpaceGeometry());
|
|
|
|
// We allow sampling for the inertial scroll if we are not in the overscroll region,
|
|
// Or if we are scrolling outwards of the overscroll region
|
|
return CurrentOverscroll == 0.f || FMath::Sign(CurrentOverscroll) != FMath::Sign(ScrollAmount);
|
|
}
|
|
|
|
int32 STableViewBase::GetNumPinnedItems() const
|
|
{
|
|
return PinnedItemsPanel->GetChildren()->Num();
|
|
}
|
|
|
|
EVisibility STableViewBase::GetPinnedItemsVisiblity() const
|
|
{
|
|
return PinnedItemsPanel->GetChildren()->Num() != 0 ? EVisibility::Visible : EVisibility::Collapsed;
|
|
}
|
|
|
|
static const TBitArray<> EmptyBitArray = TBitArray<>();
|
|
|
|
const TBitArray<>& TableViewHelpers::GetEmptyBitArray()
|
|
{
|
|
return EmptyBitArray;
|
|
}
|