1 /*
2  * Copyright (c) 2022 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "core/components_v2/list/render_list_item_group.h"
17 
18 #include "core/components/box/box_component.h"
19 #include "core/components/box/render_box.h"
20 #include "core/components/button/button_component.h"
21 #include "core/components/image/image_component.h"
22 
23 namespace OHOS::Ace::V2 {
24 
RemoveAllItems()25 void RenderListItemGroup::RemoveAllItems()
26 {
27     items_.clear();
28     ClearChildren();
29 }
30 
MakeInnerLayout()31 LayoutParam RenderListItemGroup::MakeInnerLayout()
32 {
33     Size maxSize;
34     Size minSize;
35     if (vertical_) {
36         auto maxCrossSize = GetLayoutParam().GetMaxSize().Width();
37         if (Positive(maxLaneLength_)) {
38             maxSize = Size(std::min(maxCrossSize / lanes_, maxLaneLength_), Size::INFINITE_SIZE);
39         } else {
40             maxSize = Size(maxCrossSize / lanes_, Size::INFINITE_SIZE);
41         }
42         minSize = Size(GetLayoutParam().GetMinSize().Width(), 0.0);
43     } else {
44         auto maxCrossSize = GetLayoutParam().GetMaxSize().Height();
45         if (Positive(maxLaneLength_)) {
46             maxSize = Size(Size::INFINITE_SIZE, std::min(maxCrossSize / lanes_, maxLaneLength_));
47         } else {
48             maxSize = Size(Size::INFINITE_SIZE, maxCrossSize / lanes_);
49         }
50         minSize = Size(0.0, GetLayoutParam().GetMinSize().Height());
51     }
52     return { maxSize, minSize };
53 }
54 
RequestListItem(size_t index,bool atStart)55 RefPtr<RenderListItem> RenderListItemGroup::RequestListItem(size_t index, bool atStart)
56 {
57     auto generator = itemGenerator_.Upgrade();
58     auto newItem = generator ? generator->RequestListItem(index) : RefPtr<RenderListItem>();
59     if (!newItem) {
60         return newItem;
61     }
62 
63     if (!newItem->GetVisible()) {
64         newItem->SetVisible(true);
65     }
66 
67     const auto innerLayout = MakeInnerLayout();
68     AddChild(newItem);
69     newItem->Layout(innerLayout);
70 
71     if (atStart) {
72         items_.emplace_front(newItem);
73     } else {
74         items_.emplace_back(newItem);
75     }
76     return newItem;
77 }
78 
RecycleListItem(size_t index)79 void RenderListItemGroup::RecycleListItem(size_t index)
80 {
81     auto generator = itemGenerator_.Upgrade();
82     if (generator) {
83         generator->RecycleListItem(index);
84     }
85 }
86 
RequestListItemHeader()87 RefPtr<RenderNode> RenderListItemGroup::RequestListItemHeader()
88 {
89     auto generator = itemGenerator_.Upgrade();
90     header_ =  generator ? generator->RequestListItemHeader() : RefPtr<RenderNode>();
91     if (header_) {
92         AddChild(header_);
93     }
94     return header_;
95 }
96 
RequestListItemFooter()97 RefPtr<RenderNode> RenderListItemGroup::RequestListItemFooter()
98 {
99     auto generator = itemGenerator_.Upgrade();
100     footer_ = generator ? generator->RequestListItemFooter() : RefPtr<RenderNode>();
101     if (footer_) {
102         AddChild(footer_);
103     }
104     return footer_;
105 }
106 
TotalCount()107 size_t RenderListItemGroup::TotalCount()
108 {
109     auto generator = itemGenerator_.Upgrade();
110     return generator ? generator->TotalCount() : 0;
111 }
112 
Update(const RefPtr<Component> & component)113 void RenderListItemGroup::Update(const RefPtr<Component>& component)
114 {
115     LOGI("RenderListItemGroup::Update");
116     component_ = AceType::DynamicCast<ListItemGroupComponent>(component);
117     if (!component_) {
118         return;
119     }
120     const auto& divider = component_->GetItemDivider();
121     listSpace_ = component_->GetSpace();
122     spaceWidth_ = std::max(NormalizePercentToPx(component_->GetSpace(), vertical_),
123         divider ? NormalizePercentToPx(divider->strokeWidth, vertical_) : 0.0);
124     RemoveAllItems();
125     MarkNeedLayout();
126 }
127 
RecycleStartCacheItems()128 void RenderListItemGroup::RecycleStartCacheItems()
129 {
130     double curMainPosForRecycle = startIndexOffset_;
131     size_t curIndex = startIndex_;
132     size_t lanes = lanes_ > 1 ? lanes_ : 1;
133     for (auto it = items_.begin(); it != items_.end() && currStartCacheCount_ > startCacheCount_; curIndex += lanes_) {
134         double rowSize = 0;
135         for (size_t i = 0; i < lanes && it != items_.end(); i++) {
136             const auto& child = *(it);
137             double childSize = GetMainSize(child->GetLayoutSize());
138             rowSize = std::max(childSize, rowSize);
139             // Recycle list items out of view port
140             RecycleListItem(curIndex + i);
141             it = items_.erase(it);
142         }
143         curMainPosForRecycle += rowSize + spaceWidth_;
144         startIndexOffset_ = curMainPosForRecycle;
145         startIndex_ = curIndex + lanes;
146         currStartCacheCount_--;
147     }
148 }
149 
LayoutALine(std::list<RefPtr<RenderListItem>>::iterator & it)150 double RenderListItemGroup::LayoutALine(std::list<RefPtr<RenderListItem>>::iterator& it)
151 {
152     double rowSize = 0;
153     for (size_t i = 0; i < lanes_; i++) {
154         RefPtr<RenderListItem> child;
155         if (it == items_.end()) {
156             if (i == 0) {
157                 break;
158             }
159             child = RequestListItem(startIndex_ + items_.size(), false);
160             if (!child) {
161                 break;
162             }
163             it = items_.end();
164         } else {
165             child = *(it++);
166         }
167         child->Layout(MakeInnerLayout());
168         double childSize = GetMainSize(child->GetLayoutSize());
169         rowSize = std::max(childSize, rowSize);
170     }
171     return rowSize;
172 }
173 
LayoutOrRecycleCurrentItems()174 double RenderListItemGroup::LayoutOrRecycleCurrentItems()
175 {
176     double curMainPos = startIndexOffset_;
177     size_t curIndex = startIndex_;
178     currStartCacheCount_ = 0;
179     currEndCacheCount_ = 0;
180 
181     size_t lanes = lanes_ > 1 ? lanes_ : 1;
182     for (auto it = items_.begin(); it != items_.end(); curIndex += lanes_) {
183         if (GreatNotEqual(curMainPos, endMainPos_) && currEndCacheCount_ >= endCacheCount_) {
184             for (size_t i = 0; i < lanes && it != items_.end(); i++) {
185                 // Recycle list items out of view port
186                 RecycleListItem(curIndex + i);
187                 it = items_.erase(it);
188             }
189             continue;
190         }
191         if (GreatNotEqual(curMainPos, endMainPos_)) {
192             currEndCacheCount_++;
193         }
194         double rowSize = LayoutALine(it);
195         curMainPos += rowSize + spaceWidth_;
196         if (LessNotEqual(curMainPos, startMainPos_)) {
197             currStartCacheCount_++;
198         }
199     }
200     RecycleStartCacheItems();
201     return curMainPos;
202 }
203 
RequestNewItemsAtEnd(double & curMainPos)204 void RenderListItemGroup::RequestNewItemsAtEnd(double& curMainPos)
205 {
206     size_t lanes = lanes_ > 1 ? lanes_ : 1;
207     size_t newIndex = startIndex_ + items_.size();
208     while (true) {
209         if (GreatOrEqual(curMainPos, endMainPos_) && currEndCacheCount_ >= endCacheCount_) {
210             break;
211         }
212         double rowSize = 0;
213         size_t idx = 0;
214         for (; idx < lanes; idx++) {
215             auto child = RequestListItem(newIndex + idx, false);
216             if (!child) {
217                 break;
218             }
219             double childSize = GetMainSize(child->GetLayoutSize());
220             rowSize = std::max(childSize, rowSize);
221         }
222         if (idx == 0) {
223             break;
224         }
225         if (GreatOrEqual(curMainPos, endMainPos_)) {
226             currEndCacheCount_++;
227         }
228         curMainPos += rowSize + spaceWidth_;
229         newIndex += idx;
230         if (idx < lanes) {
231             break;
232         }
233     }
234     if (startIndex_ + items_.size() >= TotalCount() && !items_.empty()) {
235         curMainPos -= spaceWidth_;
236     }
237 }
238 
RequestNewItemsAtStart()239 void RenderListItemGroup::RequestNewItemsAtStart()
240 {
241     while (true) {
242         if (LessOrEqual(startIndexOffset_, startMainPos_) && currStartCacheCount_ >= startCacheCount_) {
243             break;
244         }
245         double rowSize = 0;
246         size_t count = lanes_;
247         if (startIndex_ >= TotalCount()) {
248             count = startIndex_ % lanes_;
249             if (count == 0) {
250                 count = lanes_;
251             }
252         }
253         size_t idx = 0;
254         for (; idx < count && startIndex_ - idx > 0; idx++) {
255             auto child = RequestListItem(startIndex_ - idx - 1, true);
256             if (!child) {
257                 break;
258             }
259             double childSize = GetMainSize(child->GetLayoutSize());
260             rowSize = std::max(childSize, rowSize);
261         }
262         if (idx == 0) {
263             break;
264         }
265         if (LessOrEqual(startIndexOffset_, startMainPos_)) {
266             currStartCacheCount_++;
267         }
268         if (startIndex_ >= TotalCount()) {
269             startIndexOffset_ -= rowSize;
270         } else {
271             startIndexOffset_ -= rowSize + spaceWidth_;
272         }
273         startIndex_ -= idx;
274         if (idx < lanes_) {
275             break;
276         }
277     }
278 }
279 
LayoutHeaderFooter(bool reachEnd)280 void RenderListItemGroup::LayoutHeaderFooter(bool reachEnd)
281 {
282     if ((stickyHeader_ || startIndex_ == 0) && !header_) {
283         RequestListItemHeader();
284     } else if (!stickyHeader_ && startIndex_ > 0 && header_) {
285         RemoveChild(header_);
286         header_ = nullptr;
287     }
288 
289     if ((stickyFooter_ || reachEnd) && !footer_) {
290         RequestListItemFooter();
291     } else if (!stickyFooter_ && !reachEnd && footer_) {
292         RemoveChild(footer_);
293         footer_ = nullptr;
294     }
295 
296     if (header_) {
297         header_->Layout(GetLayoutParam());
298     }
299 
300     if (footer_) {
301         footer_->Layout(GetLayoutParam());
302     }
303 }
304 
CalculateCrossOffset(double crossSize,double childCrossSize)305 double RenderListItemGroup::CalculateCrossOffset(double crossSize, double childCrossSize)
306 {
307     double delta = crossSize - childCrossSize;
308     switch (align_) {
309         case ListItemAlign::START:
310             return 0.0;
311         case ListItemAlign::CENTER:
312             return delta / 2; /* 2 average */
313         case ListItemAlign::END:
314             return delta;
315         default:
316             LOGW("Invalid ListItemAlign: %{public}d", align_);
317             return 0.0;
318     }
319 }
320 
SetItemsPostion()321 void RenderListItemGroup::SetItemsPostion()
322 {
323     double crossSize = GetCrossSize(GetLayoutParam().GetMaxSize());
324     double layoutPos = forwardLayout_ ? (startIndexOffset_ - forwardReferencePos_) : 0.0;
325     if (header_) {
326         double crossOffset = CalculateCrossOffset(crossSize, GetCrossSize(header_->GetLayoutSize()));
327         double headerSize = GetMainSize(header_->GetLayoutSize());
328         if (stickyHeader_ && Negative(forwardReferencePos_)) {
329             double headerPos = backwardReferencePos_ - headerSize;
330             if (footer_) {
331                 headerPos -= GetMainSize(footer_->GetLayoutSize());
332             }
333             headerPos = std::min(0.0, headerPos);
334             header_->SetPosition(MakeValue<Offset>(headerPos - forwardReferencePos_, crossOffset));
335         } else {
336             auto offset = MakeValue<Offset>(layoutPos - headerSize, crossOffset);
337             header_->SetPosition(offset);
338         }
339     }
340     double laneCrossSize = GetCrossSize(GetLayoutSize()) / lanes_;
341     for (auto it = items_.begin(); it != items_.end();) {
342         double rowSize = 0;
343         for (size_t i = 0; i < lanes_ && it != items_.end(); i++) {
344             auto child = *(it++);
345             double childSize = GetMainSize(child->GetLayoutSize());
346             rowSize = std::max(childSize, rowSize);
347             double crossOffset = CalculateCrossOffset(laneCrossSize, GetCrossSize(child->GetLayoutSize()));
348             auto offset = MakeValue<Offset>(layoutPos, i * laneCrossSize + crossOffset);
349             child->SetPosition(offset);
350         }
351         layoutPos += (rowSize + spaceWidth_);
352     }
353     if (!items_.empty()) {
354         layoutPos -= spaceWidth_;
355     }
356     endIndexOffset_ = layoutPos;
357     if (footer_) {
358         double crossOffset = CalculateCrossOffset(crossSize, GetCrossSize(footer_->GetLayoutSize()));
359         if (stickyFooter_ && GreatNotEqual(backwardReferencePos_, listMainSize_)) {
360             double footerSize = GetMainSize(footer_->GetLayoutSize());
361             double footerPos = forwardReferencePos_;
362             if (header_) {
363                 footerPos += GetMainSize(header_->GetLayoutSize());
364             }
365             footerPos = std::max(footerPos, listMainSize_ - footerSize);
366             footer_->SetPosition(MakeValue<Offset>(footerPos - forwardReferencePos_, crossOffset));
367         } else {
368             auto offset = MakeValue<Offset>(layoutPos, crossOffset);
369             footer_->SetPosition(offset);
370         }
371     }
372 }
373 
PerformLayout()374 void RenderListItemGroup::PerformLayout()
375 {
376     double curMainPos = LayoutOrRecycleCurrentItems();
377     RequestNewItemsAtEnd(curMainPos);
378     RequestNewItemsAtStart();
379 
380     bool reachEnd = (startIndex_ + items_.size() >= TotalCount());
381     LayoutHeaderFooter(reachEnd);
382 
383     double headerSize = header_ && (startIndex_ == 0) ? GetMainSize(header_->GetLayoutSize()) : 0.0;
384     double footerSize = footer_ && reachEnd ? GetMainSize(footer_->GetLayoutSize()) : 0.0;
385     if (forwardLayout_ && LessNotEqual(startIndexOffset_ - headerSize, forwardReferencePos_)) {
386         startIndexOffset_ = forwardReferencePos_ + headerSize;
387         curMainPos += headerSize;
388     }
389     if (!forwardLayout_ && GreatNotEqual(curMainPos + footerSize, backwardReferencePos_)) {
390         startIndexOffset_ -= curMainPos + footerSize - backwardReferencePos_;
391         curMainPos = backwardReferencePos_ - footerSize;
392     }
393     double currMainSize = forwardLayout_ ?
394         (curMainPos + footerSize - forwardReferencePos_) : (backwardReferencePos_ - startIndexOffset_ + headerSize);
395     const auto& layoutParam = GetLayoutParam();
396     auto size = MakeValue<Size>(currMainSize, GetCrossSize(layoutParam.GetMaxSize()));
397     auto layoutSize = layoutParam.Constrain(size);
398     SetLayoutSize(layoutSize);
399     if (forwardLayout_) {
400         backwardReferencePos_ = forwardReferencePos_ + currMainSize;
401     } else {
402         forwardReferencePos_ = backwardReferencePos_ - currMainSize;
403     }
404 
405     if (startIndex_ == 0) {
406         forwardLayout_ = true;
407     }
408 
409     SetItemsPostion();
410 }
411 
GetRenderNode()412 RefPtr<RenderNode> RenderListItemGroup::GetRenderNode()
413 {
414     auto parent = renderNode_.Upgrade();
415     if (parent) {
416         return parent;
417     }
418     return Claim(this);
419 }
420 
SetNeedLayoutDeep()421 void RenderListItemGroup::SetNeedLayoutDeep()
422 {
423     SetNeedLayout(true);
424     auto topRenderNode = renderNode_.Upgrade();
425     if (topRenderNode) {
426         auto parent = GetParent().Upgrade();
427         while (parent != nullptr && topRenderNode != parent) {
428             parent->SetNeedLayout(true);
429             parent = parent->GetParent().Upgrade();
430         }
431         topRenderNode->SetNeedLayout(true);
432     }
433 }
434 
SetItemGroupLayoutParam(const ListItemLayoutParam & param)435 void RenderListItemGroup::SetItemGroupLayoutParam(const ListItemLayoutParam &param)
436 {
437     startMainPos_ = param.startMainPos;
438     endMainPos_ = param.endMainPos;
439     startCacheCount_ = param.startCacheCount;
440     endCacheCount_ = param.endCacheCount;
441     listMainSize_ = param.listMainSize;
442     maxLaneLength_ = param.maxLaneLength;
443     vertical_ = param.isVertical;
444     align_ = param.align;
445     stickyHeader_ = (param.sticky == StickyStyle::HEADER) || (param.sticky == StickyStyle::BOTH);
446     stickyFooter_ = (param.sticky == StickyStyle::FOOTER) || (param.sticky == StickyStyle::BOTH);
447     lanes_ = static_cast<size_t>(param.lanes);
448     if (!isInitialized_) {
449         isInitialized_ = true;
450         startIndex_ = param.forwardLayout ? 0 : TotalCount();
451         startIndexOffset_ = param.referencePos;
452         forwardReferencePos_ = param.referencePos;
453         backwardReferencePos_ = param.referencePos;
454     }
455     if (param.forwardLayout) {
456         startIndexOffset_ += param.referencePos - forwardReferencePos_;
457         forwardReferencePos_ = param.referencePos;
458     } else {
459         startIndexOffset_ += param.referencePos - backwardReferencePos_;
460         backwardReferencePos_ = param.referencePos;
461     }
462     forwardLayout_ = param.forwardLayout;
463 }
464 
SetChainOffset(double offset)465 void RenderListItemGroup::SetChainOffset(double offset)
466 {
467     if (NearZero(offset)) {
468         return;
469     }
470 
471     if (header_ && stickyHeader_) {
472         double crossSize = GetCrossSize(GetLayoutParam().GetMaxSize());
473         double layoutPos = forwardLayout_ ? (startIndexOffset_ - forwardReferencePos_) : spaceWidth_;
474         double crossOffset = CalculateCrossOffset(crossSize, GetCrossSize(header_->GetLayoutSize()));
475         double headerSize = GetMainSize(header_->GetLayoutSize());
476         if (Negative(forwardReferencePos_ + offset)) {
477             double headerPos = backwardReferencePos_ + offset - headerSize;
478             if (footer_) {
479                 headerPos -= GetMainSize(footer_->GetLayoutSize());
480             }
481             headerPos = std::min(0.0, headerPos);
482             header_->SetPosition(MakeValue<Offset>(headerPos - (forwardReferencePos_ + offset), crossOffset));
483         } else {
484             auto offset = MakeValue<Offset>(layoutPos - headerSize, crossOffset);
485             header_->SetPosition(offset);
486         }
487     }
488     if (footer_ && stickyFooter_) {
489         double crossSize = GetCrossSize(GetLayoutParam().GetMaxSize());
490         double crossOffset = CalculateCrossOffset(crossSize, GetCrossSize(footer_->GetLayoutSize()));
491         if (GreatNotEqual(backwardReferencePos_ + offset, listMainSize_)) {
492             double footerSize = GetMainSize(footer_->GetLayoutSize());
493             double footerPos = forwardReferencePos_ + offset;
494             if (header_) {
495                 footerPos += GetMainSize(header_->GetLayoutSize());
496             }
497             footerPos = std::max(footerPos, listMainSize_ - footerSize);
498             footer_->SetPosition(MakeValue<Offset>(footerPos - (forwardReferencePos_ + offset), crossOffset));
499         } else {
500             auto offset = MakeValue<Offset>(endIndexOffset_, crossOffset);
501             footer_->SetPosition(offset);
502         }
503     }
504 }
505 
Paint(RenderContext & context,const Offset & offset)506 void RenderListItemGroup::Paint(RenderContext& context, const Offset& offset)
507 {
508     for (const auto& child : GetItems()) {
509         PaintChild(child, context, offset);
510     }
511     PaintDivider(context);
512     if (header_) {
513         PaintChild(header_, context, offset);
514     }
515     if (footer_) {
516         PaintChild(footer_, context, offset);
517     }
518 }
519 } // namespace OHOS::Ace::V2
520