Stream Chat Unreal SDK
Loading...
Searching...
No Matches
SPaginateListWidget.h
1// Copyright 2022 Stream.IO, Inc. All Rights Reserved.
2
3#pragma once
4
5#include "Channel/ChatChannel.h"
6#include "CoreMinimal.h"
7#include "SStreamListView.h"
8
9template <class ItemType>
10class SPaginateListWidget : public SStreamListView<ItemType>
11{
12 using FSuperArguments = typename SStreamListView<ItemType>::FArguments;
13 using FCreateListViewWidgetDelegate = typename SStreamListView<ItemType>::FCreateListViewWidgetDelegate;
14
15 DECLARE_DELEGATE_TwoParams(FOnPaginatingDelegate, EPaginationDirection, EHttpRequestState);
16 DECLARE_DELEGATE_TwoParams(FDoPaginateDelegate, EPaginationDirection, TFunction<void()>);
17
18public:
19 SLATE_BEGIN_ARGS(SPaginateListWidget<ItemType>)
20 : _Limit{20}
21 , _PaginateScrollThreshold{100.f}
22 , _PaginationDirection{EPaginationDirection::Bottom}
23 , _DoPaginate{}
24 , _OnPaginating{}
25 , _ListItemsSource{nullptr}
26 , _CreateListViewWidget{}
27 {
28 }
29
30 SLATE_ARGUMENT(uint32, Limit)
31 SLATE_ARGUMENT(float, PaginateScrollThreshold)
32 SLATE_ARGUMENT(EPaginationDirection, PaginationDirection)
33 SLATE_EVENT(FDoPaginateDelegate, DoPaginate);
34 SLATE_EVENT(FOnPaginatingDelegate, OnPaginating);
35 SLATE_ARGUMENT(const TArray<ItemType>*, ListItemsSource)
36 SLATE_EVENT(FCreateListViewWidgetDelegate, CreateListViewWidget);
37
38 SLATE_END_ARGS()
39
40 void Construct(const FArguments& InArgs);
41 virtual STableViewBase::FReGenerateResults ReGenerateItems(const FGeometry& MyGeometry) override;
42
43protected:
44 void OnScroll(const double CurrentOffset);
45 void ConditionallyPaginate();
46 void Paginate(const EPaginationDirection Directions);
47
49 uint32 Limit = 20;
50 // If the scroll offset is below this value, then new messages will be fetched
51 float PaginateScrollThreshold = 100.f;
52 // The end of the scroll box which causes pagination to be triggered
53 EPaginationDirection PaginationDirection = EPaginationDirection::Bottom;
55 FDoPaginateDelegate DoPaginate;
57 FOnPaginatingDelegate OnPaginating;
58
59private:
60 void SetPaginationRequestState(const EHttpRequestState RequestState, const EPaginationDirection Direction);
61 EPaginationDirection GetDirections() const;
62 uint32 GetItemCount() const;
63
64 EPaginationDirection EndedPaginationDirections = EPaginationDirection::None;
65 EHttpRequestState PaginationRequestState = EHttpRequestState::Ended;
66 TOptional<ItemType> FirstItem;
67};
68
69template <class ItemType>
70void SPaginateListWidget<ItemType>::Construct(const FArguments& InArgs)
71{
72 SStreamListView<ItemType>::Construct(FSuperArguments()
73 .ListItemsSource(InArgs._ListItemsSource)
74 .CreateListViewWidget(InArgs._CreateListViewWidget)
75 .OnListViewScrolled(this, &SPaginateListWidget<ItemType>::OnScroll));
76
77 this->Limit = InArgs._Limit;
78 this->PaginateScrollThreshold = InArgs._PaginateScrollThreshold;
79 this->PaginationDirection = InArgs._PaginationDirection;
80 this->DoPaginate = InArgs._DoPaginate;
81 this->OnPaginating = InArgs._OnPaginating;
82}
83
84template <class ItemType>
85STableViewBase::FReGenerateResults SPaginateListWidget<ItemType>::ReGenerateItems(const FGeometry& MyGeometry)
86{
87 const STableViewBase::FReGenerateResults Result = SListView<ItemType>::ReGenerateItems(MyGeometry);
88
89 // Scroll to end on first population
90 if (!FirstItem.IsSet() && Result.LengthOfGeneratedItems > 0.)
91 {
92 if (PaginationDirection == EPaginationDirection::Top)
93 {
94 this->ScrollToBottom();
95 }
96 else if (PaginationDirection == EPaginationDirection::Bottom)
97 {
98 this->ScrollToTop();
99 OnScroll(this->CurrentScrollOffset);
100 }
101 }
102 return Result;
103}
104
105template <class ItemType>
106void SPaginateListWidget<ItemType>::OnScroll(const double CurrentOffset)
107{
108 const int32 FirstItemIndex = FMath::TruncToInt(CurrentOffset);
109 FirstItem = this->ItemsSource->IsValidIndex(FirstItemIndex) ? (*this->ItemsSource)[FirstItemIndex] : TOptional<ItemType>{};
110
111 ConditionallyPaginate();
112}
113
114template <class ItemType>
115void SPaginateListWidget<ItemType>::ConditionallyPaginate()
116{
117 // Skip if a request is already in-flight
118 if (!DoPaginate.IsBound() || PaginationRequestState == EHttpRequestState::Started)
119 {
120 return;
121 }
122
123 // Skip if currently no data
124 if (this->GetItemCount() == 0)
125 {
126 return;
127 }
128
129 // Skip if not within the threshold of either end
130 const EPaginationDirection Directions = GetDirections();
131 if (Directions == EPaginationDirection::None)
132 {
133 return;
134 }
135
136 // Skip if the paginating in an unsupported direction
137 if (!EnumHasAnyFlags(Directions, PaginationDirection))
138 {
139 return;
140 }
141
142 // Skip if no more items will exist in this direction
143 if (EnumHasAllFlags(EndedPaginationDirections, Directions))
144 {
145 return;
146 }
147
148 Paginate(Directions);
149}
150
151template <class ItemType>
152void SPaginateListWidget<ItemType>::Paginate(const EPaginationDirection Directions)
153{
154 SetPaginationRequestState(EHttpRequestState::Started, Directions);
155
156 DoPaginate.Execute(
157 Directions,
158 [WeakThis = TWeakPtr<SWidget>(this->AsShared()), OrigWidgetCount = GetItemCount(), Directions]
159 {
160 if (!WeakThis.IsValid())
161 {
162 return;
163 }
164 TSharedPtr<SPaginateListWidget> This = StaticCastSharedPtr<SPaginateListWidget>(WeakThis.Pin());
165
166 const uint32 DeltaChildrenCount = This->GetItemCount() - OrigWidgetCount;
167 if (DeltaChildrenCount == 0 || DeltaChildrenCount < This->Limit)
168 {
169 // Don't need to paginate again in this direction in the future
170 This->EndedPaginationDirections |= Directions;
171 }
172
173 This->SetPaginationRequestState(EHttpRequestState::Ended, Directions);
174
175 if (This->FirstItem.IsSet())
176 {
177 const float FirstOffset = FMath::Fractional(This->DesiredScrollOffset);
178 const int32 NewIndex = This->ItemsSource->Find(This->FirstItem.GetValue());
179 This->ScrollTo(static_cast<float>(NewIndex) + FirstOffset);
180 }
181 });
182}
183
184template <class ItemType>
185void SPaginateListWidget<ItemType>::SetPaginationRequestState(const EHttpRequestState RequestState, const EPaginationDirection Direction)
186{
187 PaginationRequestState = RequestState;
188 OnPaginating.ExecuteIfBound(Direction, RequestState);
189}
190
191template <class ItemType>
192EPaginationDirection SPaginateListWidget<ItemType>::GetDirections() const
193{
194 EPaginationDirection Directions = EPaginationDirection::None;
195 const float Height = this->GetCachedGeometry().GetLocalSize().Y;
196 if (Height == 0.f)
197 {
198 return Directions;
199 }
200
201 const float TopOffset = this->ScrollBar->DistanceFromTop() * Height;
202 const float BottomOffset = this->ScrollBar->DistanceFromBottom() * Height;
203 if (TopOffset < PaginateScrollThreshold)
204 {
205 Directions |= EPaginationDirection::Top;
206 }
207 if (BottomOffset < PaginateScrollThreshold)
208 {
209 Directions |= EPaginationDirection::Bottom;
210 }
211 return Directions;
212}
213
214template <class ItemType>
215uint32 SPaginateListWidget<ItemType>::GetItemCount() const
216{
217 return this->ItemsSource->Num();
218}
@ None
Do nothing.