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

227 lines
6.6 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Widgets/Text/STextScroller.h"
#include "Widgets/Text/STextBlock.h"
#include "Layout/ArrangedChildren.h"
#include "Layout/LayoutUtils.h"
void STextScroller::Construct(const FArguments& InArgs)
{
ScrollOptions = InArgs._ScrollOptions;
ScrollOrientation = InArgs._ScrollOrientation;
// We don't tick, we use an active ticker.
SetCanTick(false);
ChildSlot
[
InArgs._Content.Widget
];
}
void STextScroller::OnArrangeChildren(const FGeometry& AllottedGeometry, FArrangedChildren& ArrangedChildren) const
{
const EVisibility ChildVisibility = ChildSlot.GetWidget()->GetVisibility();
if (ArrangedChildren.Accepts(ChildVisibility))
{
const FVector2D ThisContentScale = GetContentScale();
const FMargin SlotPadding(LayoutPaddingWithFlow(GSlateFlowDirection, ChildSlot.GetPadding()));
const AlignmentArrangeResult XResult = AlignChild<Orient_Horizontal>(GSlateFlowDirection, AllottedGeometry.GetLocalSize().X, ChildSlot, SlotPadding, ThisContentScale.X, ScrollOrientation != Orient_Horizontal);
const AlignmentArrangeResult YResult = AlignChild<Orient_Vertical>(AllottedGeometry.GetLocalSize().Y, ChildSlot, SlotPadding, ThisContentScale.Y, ScrollOrientation != Orient_Vertical);
const FVector2D MinimumSize = ChildSlot.GetWidget()->GetDesiredSize();
ArrangedChildren.AddWidget(ChildVisibility, AllottedGeometry.MakeChild(
ChildSlot.GetWidget(),
FVector2D(XResult.Offset, YResult.Offset),
FVector2D(XResult.Size, YResult.Size)
));
}
}
int32 STextScroller::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
{
const int AxisIndex = ScrollOrientation == Orient_Vertical ? 1 : 0;
{
STextScroller* MutableThis = const_cast<STextScroller*>(this);
const float VisibleGeometry = AllottedGeometry.GetLocalSize()[AxisIndex];
const float DesiredGeometry = VisibleGeometry == 0.0f ? VisibleGeometry : ChildSlot.GetWidget()->GetDesiredSize()[AxisIndex];
if (DesiredGeometry > (VisibleGeometry + 2.0f) && IsScrollingEnabled())
{
if (!ActiveTimerHandle.IsValid())
{
MutableThis->TickerState = ETickerState::StartTicking;
MutableThis->ActiveTimerHandle = MutableThis->RegisterActiveTimer(0.0f, FWidgetActiveTimerDelegate::CreateSP(MutableThis, &STextScroller::OnScrollTextTick));
}
}
else if (ActiveTimerHandle.IsValid())
{
MutableThis->TickerState = ETickerState::StopTicking;
}
}
if (ScrollOffset != 0)
{
const float ScrollDirection = GSlateFlowDirection == EFlowDirection::LeftToRight || ScrollOrientation == Orient_Vertical ? -1 : 1;
FVector2D GeometryOffset = FVector2D::ZeroVector;
GeometryOffset[AxisIndex] = ScrollOffset * ScrollDirection;
const FGeometry ScrolledGeometry = AllottedGeometry.MakeChild(FSlateRenderTransform(GeometryOffset));
return SCompoundWidget::OnPaint(Args, ScrolledGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled);
}
else
{
return SCompoundWidget::OnPaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled);
}
}
void STextScroller::ResetScrollState()
{
FontAlpha = 1.f;
TimeElapsed = 0.f;
ScrollOffset = 0.f;
// If suspended, make sure we stay suspended until receiving an explicit call to resume
if (IsScrollingEnabled())
{
ActiveState = EActiveState::Start;
}
SetRenderOpacity(1.0f);
}
void STextScroller::StartScrolling()
{
ActiveState = EActiveState::Start;
ResetScrollState();
}
void STextScroller::SuspendScrolling()
{
ActiveState = EActiveState::Suspend;
ResetScrollState();
}
EActiveTimerReturnType STextScroller::OnScrollTextTick(double CurrentTime, float DeltaTime)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_STextScroller_OnTick);
check(TickerState != ETickerState::None);
switch (TickerState)
{
case ETickerState::StartTicking:
if (ScrollOrientation == Orient_Vertical)
{
ChildSlot.VAlign(VAlign_Top);
}
else
{
// If we need to scroll, then it's imperative that we arrange from the left, rather than fill, so that we flip to right aligning and scrolling
// to the right (potentially).
SetFlowDirectionPreference(EFlowDirectionPreference::Culture);
ChildSlot.HAlign(HAlign_Left);
}
TickerState = ETickerState::Ticking;
return EActiveTimerReturnType::Continue; // Defer to the next tick so that the layout is correct
case ETickerState::StopTicking:
if (ScrollOrientation == Orient_Vertical)
{
ChildSlot.VAlign(VAlign_Fill);
}
else
{
// If we no longer need to scroll, just inherit the flow direction.
SetFlowDirectionPreference(EFlowDirectionPreference::Inherit);
ChildSlot.HAlign(HAlign_Fill);
}
TickerState = ETickerState::None;
ResetScrollState();
ActiveTimerHandle.Reset();
return EActiveTimerReturnType::Stop; // Ticking is no longer required
default:
break;
}
const int AxisIndex = ScrollOrientation == Orient_Vertical ? 1 : 0;
const float ContentSize = ChildSlot.GetWidget()->GetDesiredSize()[AxisIndex];
TimeElapsed += DeltaTime;
switch (ActiveState)
{
case EActiveState::FadeIn:
{
FontAlpha = FMath::Clamp<float>(TimeElapsed / ScrollOptions.FadeInDelay, 0.f, 1.f);
if (TimeElapsed >= ScrollOptions.FadeInDelay)
{
FontAlpha = 1.f;
TimeElapsed = 0.f;
ScrollOffset = 0.f;
ActiveState = EActiveState::Start;
}
break;
}
case EActiveState::Start:
{
TimeElapsed = 0.f;
ScrollOffset = 0.f;
ActiveState = EActiveState::StartWait;
break;
}
case EActiveState::StartWait:
{
if (TimeElapsed >= ScrollOptions.StartDelay)
{
TimeElapsed = 0.f;
ScrollOffset = 0.f;
ActiveState = EActiveState::Scrolling;
}
break;
}
case EActiveState::Scrolling:
{
ScrollOffset += ScrollOptions.Speed * DeltaTime;
if ((ScrollOffset + GetCachedGeometry().GetLocalSize()[AxisIndex]) >= ContentSize)
{
TimeElapsed = 0.0f;
ActiveState = EActiveState::Stop;
}
break;
}
case EActiveState::Stop:
{
TimeElapsed = 0.f;
ActiveState = EActiveState::StopWait;
break;
}
case EActiveState::StopWait:
{
if (TimeElapsed >= ScrollOptions.EndDelay)
{
TimeElapsed = 0.f;
ActiveState = EActiveState::FadeOut;
}
break;
}
case EActiveState::FadeOut:
{
FontAlpha = 1.0f - FMath::Clamp<float>(TimeElapsed / ScrollOptions.FadeOutDelay, 0.f, 1.f);
if (TimeElapsed >= ScrollOptions.FadeOutDelay)
{
FontAlpha = 0.0f;
TimeElapsed = 0.0f;
ScrollOffset = 0.0f;
ActiveState = EActiveState::FadeIn;
}
break;
}
}
SetRenderOpacity(FontAlpha);
Invalidate(EInvalidateWidgetReason::Paint);
return EActiveTimerReturnType::Continue;
}