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 ¶m)
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