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

435 lines
11 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Widgets/Layout/SScrollBar.h"
#include "Widgets/Layout/SScrollBarTrack.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/Layout/SSpacer.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Layout/SBox.h"
#include "Framework/Application/SlateApplication.h"
void SScrollBar::Construct(const FArguments& InArgs)
{
OnUserScrolled = InArgs._OnUserScrolled;
OnScrollBarVisibilityChanged = InArgs._OnScrollBarVisibilityChanged;
Orientation = InArgs._Orientation;
DragFocusCause = InArgs._DragFocusCause;
UserVisibility = InArgs._Visibility;
check(InArgs._Style);
SetStyle(InArgs._Style);
const TAttribute<FVector2D> Thickness = InArgs._Thickness.IsSet() ? InArgs._Thickness : FVector2D(InArgs._Style->Thickness, InArgs._Style->Thickness);
EHorizontalAlignment HorizontalAlignment = Orientation == Orient_Vertical ? HAlign_Center : HAlign_Fill;
EVerticalAlignment VerticalAlignment = Orientation == Orient_Vertical ? VAlign_Fill : VAlign_Center;
bHideWhenNotInUse = InArgs._HideWhenNotInUse;
bPreventThrottling = InArgs._PreventThrottling;
bIsScrolling = false;
LastInteractionTime = 0;
SBorder::Construct( SBorder::FArguments()
.BorderImage(FCoreStyle::Get().GetBrush("NoBorder"))
.Padding(InArgs._Padding)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.FillHeight(1)
[
SNew(SBorder)
.BorderImage(BackgroundBrush)
.HAlign(HorizontalAlignment)
.VAlign(VerticalAlignment)
.Padding(0.f)
[
SAssignNew(Track, SScrollBarTrack)
.Orientation(InArgs._Orientation)
.TopSlot()
[
SNew(SBox)
.HAlign(HorizontalAlignment)
.VAlign(VerticalAlignment)
[
SAssignNew(TopImage, SImage)
.Image(TopBrush)
]
]
.ThumbSlot()
[
SAssignNew(DragThumb, SBorder)
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.Padding(0.f)
[
SAssignNew(ThicknessSpacer, SSpacer)
.Size(Thickness)
]
]
.BottomSlot()
[
SNew(SBox)
.HAlign(HorizontalAlignment)
.VAlign(VerticalAlignment)
[
SAssignNew(BottomImage, SImage)
.Image(BottomBrush)
]
]
]
]
]
);
SetEnabled(TAttribute<bool>( Track.ToSharedRef(), &SScrollBarTrack::IsNeeded ));
SetScrollBarAlwaysVisible(InArgs._AlwaysShowScrollbar);
bAlwaysShowScrollbarTrack = InArgs._AlwaysShowScrollbarTrack;
}
void SScrollBar::SetOnUserScrolled( const FOnUserScrolled& InHandler )
{
OnUserScrolled = InHandler;
}
void SScrollBar::SetOnScrollBarVisibilityChanged( const FOnScrollBarVisibilityChanged& InHandler )
{
OnScrollBarVisibilityChanged = InHandler;
}
void SScrollBar::SetState( float InOffsetFraction, float InThumbSizeFraction, bool bCallOnUserScrolled )
{
if ( Track->DistanceFromTop() != InOffsetFraction || Track->GetThumbSizeFraction() != InThumbSizeFraction )
{
// Note that the maximum offset depends on how many items fit per screen
// It is 1.0f-InThumbSizeFraction.
Track->SetSizes(InOffsetFraction, InThumbSizeFraction);
GetVisibilityAttribute().UpdateValue();
LastInteractionTime = FSlateApplication::Get().GetCurrentTime();
if (bCallOnUserScrolled)
{
OnUserScrolled.ExecuteIfBound(InOffsetFraction);
}
}
}
void SScrollBar::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
{
SBorder::Tick(AllottedGeometry, InCurrentTime, InDeltaTime);
DragThumb->SetBorderImage(GetDragThumbImage());
DragThumb->SetColorAndOpacity(GetThumbOpacity());
TopImage->SetColorAndOpacity(GetTrackOpacity());
BottomImage->SetColorAndOpacity(GetTrackOpacity());
}
FReply SScrollBar::OnMouseButtonDown( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
{
if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton && IsNeeded())
{
FGeometry ThumbGeometry = FindChildGeometry(MyGeometry, DragThumb.ToSharedRef());
if (DragThumb->IsDirectlyHovered())
{
// Clicking on the scrollbar drag thumb
if( Orientation == Orient_Horizontal )
{
DragGrabOffset = ThumbGeometry.AbsoluteToLocal( MouseEvent.GetScreenSpacePosition() ).X;
}
else
{
DragGrabOffset = ThumbGeometry.AbsoluteToLocal( MouseEvent.GetScreenSpacePosition() ).Y;
}
bDraggingThumb = true;
}
else if (OnUserScrolled.IsBound())
{
// Clicking in the non drag thumb area of the scrollbar
DragGrabOffset = Orientation == Orient_Horizontal ? (ThumbGeometry.GetLocalSize().X * 0.5f) : (ThumbGeometry.GetLocalSize().Y * 0.5f);
bDraggingThumb = true;
ExecuteOnUserScrolled( MyGeometry, MouseEvent );
}
}
if( bDraggingThumb )
{
FReply ReturnReply = FReply::Handled().CaptureMouse(AsShared()).SetUserFocus(AsShared(), DragFocusCause);
if (bPreventThrottling)
{
ReturnReply.PreventThrottling();
}
return ReturnReply;
}
else
{
return FReply::Unhandled();
}
}
FReply SScrollBar::OnMouseButtonUp( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
{
if ( MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton )
{
bDraggingThumb = false;
return FReply::Handled().ReleaseMouseCapture();
}
else
{
return FReply::Unhandled();
}
}
FReply SScrollBar::OnMouseMove( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
{
if ( this->HasMouseCapture() )
{
if (!MouseEvent.GetCursorDelta().IsZero())
{
if (OnUserScrolled.IsBound())
{
ExecuteOnUserScrolled(MyGeometry, MouseEvent);
}
return FReply::Handled();
}
}
return FReply::Unhandled();
}
void SScrollBar::OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
SBorder::OnMouseEnter(MyGeometry, MouseEvent);
LastInteractionTime = FSlateApplication::Get().GetCurrentTime();
}
void SScrollBar::OnMouseLeave(const FPointerEvent& MouseEvent)
{
SBorder::OnMouseLeave(MouseEvent);
LastInteractionTime = FSlateApplication::Get().GetCurrentTime();
}
void SScrollBar::ExecuteOnUserScrolled( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
{
const int32 AxisId = (Orientation == Orient_Horizontal) ? 0 : 1;
const FGeometry TrackGeometry = FindChildGeometry( MyGeometry, Track.ToSharedRef() );
const float UnclampedOffsetInTrack = TrackGeometry.AbsoluteToLocal( MouseEvent.GetScreenSpacePosition() )[ AxisId ] - DragGrabOffset;
const float TrackSizeBiasedByMinThumbSize = TrackGeometry.GetLocalSize()[ AxisId ] - Track->GetMinThumbSize();
const float ThumbOffsetInTrack = FMath::Clamp( UnclampedOffsetInTrack, 0.0f, TrackSizeBiasedByMinThumbSize );
const float ThumbOffset = ThumbOffsetInTrack / TrackSizeBiasedByMinThumbSize;
OnUserScrolled.ExecuteIfBound( ThumbOffset );
}
bool SScrollBar::IsNeeded() const
{
return Track->IsNeeded();
}
float SScrollBar::DistanceFromTop() const
{
return Track->DistanceFromTop();
}
float SScrollBar::DistanceFromBottom() const
{
return Track->DistanceFromBottom();
}
float SScrollBar::ThumbSizeFraction() const
{
return Track->GetThumbSizeFraction();
}
SScrollBar::SScrollBar()
: bDraggingThumb(false)
, DragGrabOffset( 0.0f )
{
}
SScrollBar::~SScrollBar() = default;
FSlateColor SScrollBar::GetTrackOpacity() const
{
if (bDraggingThumb || IsHovered())
{
return FLinearColor(1,1,1,1);
}
else if ((bAlwaysShowScrollbarTrack && Track->GetThumbSizeFraction() > KINDA_SMALL_NUMBER) || AlwaysShowScrollbar())
{
return FLinearColor(1, 1, 1, 0.5f);
}
else
{
return FLinearColor(1,1,1,0);
}
}
FLinearColor SScrollBar::GetThumbOpacity() const
{
if (Track->GetThumbSizeFraction() <= 0.0f)
{
return FLinearColor(1, 1, 1, 0);
}
else if ( bDraggingThumb || IsHovered() )
{
return FLinearColor(1,1,1,1);
}
else
{
if ( bHideWhenNotInUse && !bAlwaysShowScrollbar )
{
const double LastInteractionDelta = bIsScrolling ? 0 : ( FSlateApplication::Get().GetCurrentTime() - LastInteractionTime );
float ThumbOpacity = FMath::Lerp(1.0f, 0.0f, FMath::Clamp((float)( ( LastInteractionDelta - 0.2 ) / 0.2 ), 0.0f, 1.0f));
return FLinearColor(1, 1, 1, ThumbOpacity);
}
else
{
return FLinearColor(1, 1, 1, 0.75f);
}
}
}
void SScrollBar::BeginScrolling()
{
bIsScrolling = true;
}
void SScrollBar::EndScrolling()
{
bIsScrolling = false;
LastInteractionTime = FSlateApplication::Get().GetCurrentTime();
}
const FSlateBrush* SScrollBar::GetDragThumbImage() const
{
if ( bDraggingThumb )
{
return DraggedThumbImage;
}
else if (IsHovered())
{
return HoveredThumbImage;
}
else
{
return NormalThumbImage;
}
}
EVisibility SScrollBar::ShouldBeVisible() const
{
const EVisibility CurrentVisibility = GetVisibility();
EVisibility NewVisibility = ScrollbarDisabledVisibility;
if ( this->HasMouseCapture() )
{
NewVisibility = EVisibility::Visible;
}
else if( Track->IsNeeded() )
{
NewVisibility = UserVisibility.Get();
}
if (NewVisibility != CurrentVisibility)
{
OnScrollBarVisibilityChanged.ExecuteIfBound(NewVisibility);
}
return NewVisibility;
}
bool SScrollBar::IsScrolling() const
{
return bDraggingThumb;
}
EOrientation SScrollBar::GetOrientation() const
{
return Orientation;
}
void SScrollBar::SetStyle(const FScrollBarStyle* InStyle)
{
const FScrollBarStyle* Style = InStyle;
if (Style == nullptr)
{
FArguments Defaults;
Style = Defaults._Style;
}
check(Style);
NormalThumbImage = &Style->NormalThumbImage;
HoveredThumbImage = &Style->HoveredThumbImage;
DraggedThumbImage = &Style->DraggedThumbImage;
if (Orientation == Orient_Vertical)
{
BackgroundBrush = &Style->VerticalBackgroundImage;
TopBrush = &Style->VerticalTopSlotImage;
BottomBrush = &Style->VerticalBottomSlotImage;
}
else
{
BackgroundBrush = &Style->HorizontalBackgroundImage;
TopBrush = &Style->HorizontalTopSlotImage;
BottomBrush = &Style->HorizontalBottomSlotImage;
}
InvalidateStyle();
}
void SScrollBar::InvalidateStyle()
{
Invalidate(EInvalidateWidgetReason::Layout);
}
void SScrollBar::SetDragFocusCause(EFocusCause InDragFocusCause)
{
DragFocusCause = InDragFocusCause;
}
void SScrollBar::SetThickness(TAttribute<FVector2D> InThickness)
{
ThicknessSpacer->SetSize(InThickness);
}
void SScrollBar::SetScrollBarAlwaysVisible(bool InAlwaysVisible)
{
bAlwaysShowScrollbar = InAlwaysVisible;
if ( InAlwaysVisible )
{
SetVisibility(EVisibility::Visible);
}
else
{
SetVisibility(TAttribute<EVisibility>(SharedThis(this), &SScrollBar::ShouldBeVisible));
}
Track->SetIsAlwaysVisible(InAlwaysVisible);
}
void SScrollBar::SetScrollBarTrackAlwaysVisible(bool InAlwaysVisible)
{
// Doesn't need to be invalidated here, tick updates these values.
bAlwaysShowScrollbarTrack = InAlwaysVisible;
}
SLATE_API void SScrollBar::SetScrollbarDisabledVisibility(EVisibility InVisibility)
{
ScrollbarDisabledVisibility = InVisibility;
}
bool SScrollBar::AlwaysShowScrollbar() const
{
return bAlwaysShowScrollbar;
}