// Copyright Epic Games, Inc. All Rights Reserved. #include "Components/ListViewBase.h" #include "UMGPrivate.h" #include "Widgets/Text/STextBlock.h" #include "TimerManager.h" #include "Engine/Blueprint.h" #include "Editor/WidgetCompilerLog.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(ListViewBase) #define LOCTEXT_NAMESPACE "UMG" UListViewBase::UListViewBase(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) , EntryWidgetPool(*this) { bIsVariable = true; SetClipping(EWidgetClipping::ClipToBounds); } #if WITH_EDITOR const FText UListViewBase::GetPaletteCategory() { return LOCTEXT("Lists", "Lists"); } void UListViewBase::ValidateCompiledDefaults(IWidgetCompilerLog& CompileLog) const { if (!EntryWidgetClass) { CompileLog.Error(FText::Format(LOCTEXT("Error_ListViewBase_MissingEntryClass", "{0} has no EntryWidgetClass specified - required for any UListViewBase to function."), FText::FromString(GetName()))); } else if (!EntryWidgetClass->ImplementsInterface(UUserListEntry::StaticClass())) { CompileLog.Error(FText::Format(LOCTEXT("Error_ListViewBase_EntryClassNotImplementingInterface", "'{0}' has EntryWidgetClass property set to'{1}' and that Class doesn't implement User List Entry Interface - required for any UListViewBase to function."), FText::FromString(GetName()), FText::FromString(EntryWidgetClass->GetName()))); } } #endif void UListViewBase::RegenerateAllEntries() { EntryWidgetPool.ReleaseAll(); GeneratedEntriesToAnnounce.Reset(); if (MyTableViewBase.IsValid()) { MyTableViewBase->RebuildList(); } } void UListViewBase::ScrollToTop() { if (MyTableViewBase.IsValid()) { MyTableViewBase->ScrollToTop(); } } void UListViewBase::ScrollToBottom() { if (MyTableViewBase.IsValid()) { MyTableViewBase->ScrollToBottom(); } } void UListViewBase::SetScrollOffset(const float InScrollOffset) { if (MyTableViewBase.IsValid()) { MyTableViewBase->SetScrollOffset(InScrollOffset); } } void UListViewBase::EndInertialScrolling() { if (MyTableViewBase.IsValid()) { MyTableViewBase->EndInertialScrolling(); } } void UListViewBase::SetWheelScrollMultiplier(float NewWheelScrollMultiplier) { WheelScrollMultiplier = NewWheelScrollMultiplier; if (MyTableViewBase) { MyTableViewBase->SetWheelScrollMultiplier(GetGlobalScrollAmount() * NewWheelScrollMultiplier); } } void UListViewBase::SetScrollbarVisibility(ESlateVisibility InVisibility) { if (MyTableViewBase) { MyTableViewBase->SetScrollbarVisibility(UWidget::ConvertSerializedVisibilityToRuntime(InVisibility)); } } UMG_API void UListViewBase::SetIsPointerScrollingEnabled(bool bInIsPointerScrollingEnabled) { bIsPointerScrollingEnabled = bInIsPointerScrollingEnabled; if (MyTableViewBase) { MyTableViewBase->SetIsPointerScrollingEnabled(bInIsPointerScrollingEnabled); } } UMG_API void UListViewBase::SetIsGamepadScrollingEnabled(bool bInIsGamepadScrollingEnabled) { bIsGamepadScrollingEnabled = bInIsGamepadScrollingEnabled; if (MyTableViewBase) { MyTableViewBase->SetIsGamepadScrollingEnabled(bInIsGamepadScrollingEnabled); } } const TArray& UListViewBase::GetDisplayedEntryWidgets() const { return EntryWidgetPool.GetActiveWidgets(); } float UListViewBase::GetScrollOffset() const { if (MyTableViewBase.IsValid()) { return MyTableViewBase->GetScrollOffset(); } return 0.0f; } TSharedRef UListViewBase::RebuildWidget() { FText ErrorText; if (!EntryWidgetClass) { ErrorText = LOCTEXT("Error_MissingEntryWidgetClass", "No EntryWidgetClass specified on this list.\nEven if doing custom stuff, this is always required as a fallback."); } #if WITH_EDITOR // if the BP was cooked already, then the ClassGeneratedBy will be null, so nothing to check else if (!EntryWidgetClass->bCooked) { UBlueprint* EntryWidgetBP = Cast(EntryWidgetClass->ClassGeneratedBy); if (!EntryWidgetBP) { ErrorText = FText::Format(LOCTEXT("Error_NonBPEntryWidget", "EntryWidgetClass [{0}] is not a Blueprint class"), FText::FromString(EntryWidgetClass->GetName())); } else if (EntryWidgetBP->Status == BS_Error) { ErrorText = FText::Format(LOCTEXT("Error_CompilationError", "EntryWidget BP [{0}] has not compiled successfully"), FText::FromString(EntryWidgetBP->GetName())); } } #endif if (!ErrorText.IsEmpty()) { return SNew(STextBlock) .Text(ErrorText); } MyTableViewBase = RebuildListWidget(); MyTableViewBase->SetIsScrollAnimationEnabled(bEnableScrollAnimation); MyTableViewBase->SetScrollingAnimationInterpolationSpeed(ScrollingAnimationInterpolationSpeed); MyTableViewBase->SetEnableTouchAnimatedScrolling(bInEnableTouchAnimatedScrolling); MyTableViewBase->SetIsRightClickScrollingEnabled(bEnableRightClickScrolling); MyTableViewBase->SetIsTouchScrollingEnabled(bEnableTouchScrolling); MyTableViewBase->SetIsPointerScrollingEnabled(bIsPointerScrollingEnabled); MyTableViewBase->SetFixedLineScrollOffset(bEnableFixedLineOffset ? TOptional(FixedLineScrollOffset) : TOptional()); MyTableViewBase->SetWheelScrollMultiplier(GetGlobalScrollAmount() * WheelScrollMultiplier); return MyTableViewBase.ToSharedRef(); } void UListViewBase::ReleaseSlateResources(bool bReleaseChildren) { Super::ReleaseSlateResources(bReleaseChildren); MyTableViewBase.Reset(); EntryWidgetPool.ResetPool(); GeneratedEntriesToAnnounce.Reset(); } void UListViewBase::SynchronizeProperties() { Super::SynchronizeProperties(); if (MyTableViewBase) { MyTableViewBase->SetIsScrollAnimationEnabled(bEnableScrollAnimation); MyTableViewBase->SetScrollingAnimationInterpolationSpeed(ScrollingAnimationInterpolationSpeed); MyTableViewBase->SetEnableTouchAnimatedScrolling(bInEnableTouchAnimatedScrolling); MyTableViewBase->SetIsRightClickScrollingEnabled(bEnableRightClickScrolling); MyTableViewBase->SetIsTouchScrollingEnabled(bEnableTouchScrolling); MyTableViewBase->SetIsPointerScrollingEnabled(bIsPointerScrollingEnabled); MyTableViewBase->SetAllowOverscroll(AllowOverscroll ? EAllowOverscroll::Yes : EAllowOverscroll::No); MyTableViewBase->SetFixedLineScrollOffset(bEnableFixedLineOffset ? TOptional(FixedLineScrollOffset) : TOptional()); MyTableViewBase->SetWheelScrollMultiplier(GetGlobalScrollAmount() * WheelScrollMultiplier); MyTableViewBase->SetSelectItemOnNavigation(bSelectItemOnNavigation); } #if WITH_EDITORONLY_DATA if (IsDesignTime() && MyTableViewBase.IsValid()) { bNeedsToCallRefreshDesignerItems = true; OnRefreshDesignerItems(); if (!ensureMsgf(!bNeedsToCallRefreshDesignerItems, TEXT("[%s] does not call RefreshDesignerItems from within OnRefreshDesignerItems. Please do so to support design-time previewing of list entries."), *GetClass()->GetName())) { bNeedsToCallRefreshDesignerItems = false; } } #endif } TSharedRef UListViewBase::RebuildListWidget() { ensureMsgf(false, TEXT("All children of UListViewBase must implement RebuildListWidget using one of the static ITypedUMGListView::ConstructX functions")); return SNew(SListView>); } void UListViewBase::RequestRefresh() { if (MyTableViewBase.IsValid()) { MyTableViewBase->RequestListRefresh(); } } void UListViewBase::HandleRowReleased(const TSharedRef& Row) { UUserWidget* EntryWidget = StaticCastSharedRef(Row)->GetUserWidget(); if (ensure(EntryWidget)) { EntryWidgetPool.Release(EntryWidget); if (!IsDesignTime()) { GeneratedEntriesToAnnounce.Remove(EntryWidget); OnEntryWidgetReleased().Broadcast(*EntryWidget); NativeOnEntryReleased(EntryWidget); BP_OnEntryReleased.Broadcast(EntryWidget); } } } void UListViewBase::FinishGeneratingEntry(UUserWidget& GeneratedEntry) { if (!IsDesignTime()) { // Announcing the row generation now is just a bit too soon, as we haven't finished generating the row as far as the underlying list is concerned. // So we cache the row/item pair here and announce their generation on the next tick GeneratedEntriesToAnnounce.AddUnique(&GeneratedEntry); if (!EntryGenAnnouncementTimerHandle.IsValid()) { if (UWorld* World = GetWorld()) { FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateWeakLambda(this, [this](float InDelta) { HandleAnnounceGeneratedEntries(); return false; })); } } } } void UListViewBase::HandleAnnounceGeneratedEntries() { EntryGenAnnouncementTimerHandle.Invalidate(); for (TWeakObjectPtr EntryWidget : GeneratedEntriesToAnnounce) { if (EntryWidget.IsValid()) { OnEntryWidgetGenerated().Broadcast(*EntryWidget); NativeOnEntryGenerated(EntryWidget.Get()); BP_OnEntryGenerated.Broadcast(EntryWidget.Get()); } } GeneratedEntriesToAnnounce.Empty(); NativeOnEntriesGenerated(); BP_OnEntriesGenerated.Broadcast(GetDisplayedEntryWidgets().Num()); } #undef LOCTEXT_NAMESPACE