1 /*
2  * Copyright (c) 2021-2024 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/indexer/render_indexer.h"
17 
18 #include "base/log/event_report.h"
19 
20 namespace OHOS::Ace {
21 namespace {
22 
23 constexpr Dimension FOCUS_PADDING = 2.0_vp;
24 
25 } // namespace
26 
Create()27 RefPtr<RenderNode> RenderIndexer::Create()
28 {
29     LOGI("[indexer] Create RenderIndexer");
30     return AceType::MakeRefPtr<RenderIndexer>();
31 }
32 
RenderIndexer()33 RenderIndexer::RenderIndexer()
34 {
35     LOGI("[indexer] touchRecognizer_ init");
36     touchRecognizer_ = AceType::MakeRefPtr<RawRecognizer>();
37     touchRecognizer_->SetOnTouchDown([wp = AceType::WeakClaim(this)](const TouchEventInfo& info) {
38         auto sp = wp.Upgrade();
39         if (sp) {
40             sp->HandleTouchDown(info);
41         }
42     });
43     touchRecognizer_->SetOnTouchUp([wp = AceType::WeakClaim(this)](const TouchEventInfo& info) {
44         auto sp = wp.Upgrade();
45         if (sp) {
46             sp->HandleTouchUp(info);
47         }
48     });
49     touchRecognizer_->SetOnTouchMove([wp = AceType::WeakClaim(this)](const TouchEventInfo& info) {
50         auto sp = wp.Upgrade();
51         if (sp) {
52             sp->HandleTouchMove(info);
53         }
54     });
55 
56     // register this listener for consuming the drag events.
57     dragRecognizer_ = AceType::MakeRefPtr<VerticalDragRecognizer>();
58     dragRecognizer_->SetOnDragStart([weakIndex = AceType::WeakClaim(this)](const DragStartInfo& info) {
59         auto indexer = weakIndex.Upgrade();
60         if (indexer) {
61             indexer->HandleDragStart();
62         }
63     });
64     dragRecognizer_->SetOnDragEnd([weakIndex = AceType::WeakClaim(this)](const DragEndInfo& info) {
65         auto indexer = weakIndex.Upgrade();
66         if (indexer) {
67             indexer->HandleDragEnd();
68         }
69     });
70 }
71 
PerformLayout()72 void RenderIndexer::PerformLayout()
73 {
74     UpdateItems();
75     if (itemCount_ <= 0) {
76         SetLayoutSize(Size());
77         return;
78     }
79 
80     // calculate self and children size,
81     if (NearZero(itemSize_)) {
82         LOGE("[indexer] Invalid Item size:%{public}lf", itemSize_);
83         return;
84     }
85 
86     // calculate the size of the items
87     const LayoutParam& layoutSetByParent = GetLayoutParam();
88     Size sizeMax = layoutSetByParent.GetMaxSize();
89     if (LessOrEqual(paddingY_ * DOUBLE + itemCount_ * itemSize_, sizeMax.Height())) {
90         itemSizeRender_ = itemSize_;
91     } else {
92         double tempSize = (sizeMax.Height() - paddingY_ * DOUBLE) / itemCount_;
93         itemSizeRender_ = tempSize;
94     }
95 
96     InitFocusedItem();
97 
98     LayoutParam childrenLayout;
99     childrenLayout.SetMinSize(Size(0.0, 0.0));
100 
101     for (const auto& item : GetChildren()) {
102         item->Layout(childrenLayout);
103     }
104     // then set the position of children
105     Offset position;
106     int32_t count = 0;
107     for (const auto& item : GetChildren()) {
108         if (!AceType::InstanceOf<RenderIndexerItem>(item)) {
109             continue;
110         }
111         position.SetX(paddingX_);
112         position.SetY(paddingY_ + count * itemSizeRender_);
113         item->SetPosition(position);
114         count++;
115     }
116 
117     double indexerWidth = paddingX_ + itemSizeRender_ + paddingX_;
118     double indexerHeight = paddingY_ + count * itemSizeRender_ + paddingY_;
119     SetLayoutSize(Size(indexerWidth, indexerHeight));
120 
121     if (IsValidBubbleBox()) {
122         const double BUBBLE_OFFSET = 200.0;
123         Offset bubblePosition;
124         bubblePosition.SetX(NormalizeToPx(BUBBLE_POSITION_X) - BUBBLE_OFFSET);
125         bubblePosition.SetY(NormalizeToPx(BUBBLE_POSITION_Y) - BUBBLE_OFFSET);
126         bubbleDisplay_->SetPosition(bubblePosition);
127     }
128 }
129 
Update(const RefPtr<Component> & component)130 void RenderIndexer::Update(const RefPtr<Component>& component)
131 {
132     RefPtr<IndexerComponent> indexerComponent = AceType::DynamicCast<IndexerComponent>(component);
133     if (!indexerComponent) {
134         LOGE("[indexer] Update Get component failed");
135         EventReport::SendRenderException(RenderExcepType::RENDER_COMPONENT_ERR);
136         return;
137     }
138     nonItemCount_ = indexerComponent->GetNonItemCount();
139     bubbleEnabled_ = indexerComponent->IsBubbleEnabled();
140     bubbleText_ = indexerComponent->GetBubbleTextComponent();
141     controller_ = indexerComponent->GetController();
142     if (controller_) {
143         // handle list move
144         controller_->AddFirstChangedCallback([weakIndex = AceType::WeakClaim(this)](int32_t index) {
145             auto indexer = weakIndex.Upgrade();
146             if (indexer) {
147                 indexer->UpdateSection(index);
148             }
149         });
150         // handle watch rotation event
151         controller_->AddIndexerStateQueryCallback([weakIndex = AceType::WeakClaim(this)]() {
152             auto indexer = weakIndex.Upgrade();
153             if (indexer) {
154                 return indexer->NeedRotation();
155             }
156             return false;
157         });
158     }
159     // update item information
160     auto context = GetContext().Upgrade();
161     if (context) {
162         itemSize_ = context->NormalizeToPx(indexerComponent->GetItemSize());
163     }
164     itemSizeRender_ = itemSize_;
165     itemCount_ = indexerComponent->GetItemCount();
166     circleMode_ = indexerComponent->GetCircleMode();
167     LOGI("[indexer] Init data, itemSizeRender_:%{public}lf, itemCount_:%{public}d", itemSizeRender_, itemCount_);
168 
169     InitIndexTable();
170     MarkNeedLayout();
171 }
172 
UpdateSection(int32_t index)173 void RenderIndexer::UpdateSection(int32_t index)
174 {
175     auto context = GetContext().Upgrade();
176     if (!context || !context->GetTaskExecutor()) {
177         return;
178     }
179 
180     context->GetTaskExecutor()->PostTask(
181         [weakIndexer = AceType::WeakClaim(this), index]() {
182             auto indexer = weakIndexer.Upgrade();
183             if (indexer) {
184                 indexer->MoveSection(index);
185             }
186         },
187         TaskExecutor::TaskType::UI, "ArkUIIndexerUpdateSection");
188 }
189 
InitIndexTable()190 void RenderIndexer::InitIndexTable()
191 {
192     LOGI("[indexer] InitIndexTable itemCount_:%{public}d", itemCount_);
193     for (int32_t i = 0; i <= INDEXER_ITEM_MAX_COUNT; ++i) {
194         itemInfo_[i][TABLE_ANGLE] = i;
195         itemInfo_[i][TABLE_POSITION_X] = paddingX_;
196         itemInfo_[i][TABLE_POSITION_Y] = paddingY_ + i * itemSizeRender_;
197     }
198 }
199 
UpdateItems()200 void RenderIndexer::UpdateItems()
201 {
202     if (static_cast<uint32_t>(nonItemCount_) + items_.size() == GetChildren().size()) {
203         return;
204     }
205     items_.clear();
206     for (auto item : GetChildren()) {
207         if (AceType::InstanceOf<RenderIndexerItem>(item)) {
208             items_.push_back(item);
209         }
210     }
211 }
212 
InitFocusedItem()213 void RenderIndexer::InitFocusedItem()
214 {
215     // init first focus item
216     if (focusedItem_ < 0 || isInitFocus_) {
217         if (focusedItem_ >= 0) {
218             auto item = GetSpecificItem(focusedItem_);
219             if (item) {
220                 item->SetClicked(false);
221             }
222         }
223         focusedItem_ = 0;
224         for (auto item : items_) {
225             RefPtr<RenderIndexerItem> indexerItem = AceType::DynamicCast<RenderIndexerItem>(item);
226             if (indexerItem && indexerItem->GetKeyCount() > 0) {
227                 LOGI("[indexer] Init focusedItem index:%{public}d", focusedItem_);
228                 indexerItem->SetClicked(true);
229                 break;
230             }
231             focusedItem_++;
232         }
233     }
234 }
235 
HandleTouchDown(const TouchEventInfo & info)236 void RenderIndexer::HandleTouchDown(const TouchEventInfo& info)
237 {
238     if (info.GetTouches().empty()) {
239         return;
240     }
241     touchPostion_ = info.GetTouches().front().GetLocalLocation();
242     HandleTouched(touchPostion_);
243     clicked_ = true;
244     MarkNeedLayout();
245 }
246 
HandleTouchUp(const TouchEventInfo & info)247 void RenderIndexer::HandleTouchUp(const TouchEventInfo& info)
248 {
249     if (!info.GetTouches().empty()) {
250         touchPostion_ = info.GetTouches().front().GetLocalLocation();
251     }
252     HandleTouched(touchPostion_);
253     if (clicked_) {
254         clicked_ = false;
255         MarkNeedLayout();
256     }
257 }
258 
HandleTouchMove(const TouchEventInfo & info)259 void RenderIndexer::HandleTouchMove(const TouchEventInfo& info)
260 {
261     if (info.GetTouches().empty()) {
262         return;
263     }
264     touchPostion_ = info.GetTouches().front().GetLocalLocation();
265     HandleTouched(touchPostion_);
266     if (clicked_) {
267         clicked_ = true;
268         MarkNeedLayout();
269     }
270 }
271 
HandleDragStart()272 void RenderIndexer::HandleDragStart() {}
273 
HandleDragEnd()274 void RenderIndexer::HandleDragEnd() {}
275 
TouchTest(const Point & globalPoint,const Point & parentLocalPoint,const TouchRestrict & touchRestrict,TouchTestResult & result)276 bool RenderIndexer::TouchTest(const Point& globalPoint, const Point& parentLocalPoint,
277     const TouchRestrict& touchRestrict, TouchTestResult& result)
278 {
279     if (GetDisableTouchEvent() || disabled_) {
280         return false;
281     }
282     auto focusedNode = GetSpecificItem(focusedIndex_);
283     if (focusedNode) {
284         focusedNode->SetFocused(false);
285     }
286     // Since the paintRect is relative to parent, use parent local point to perform touch test.
287     if (GetPaintRect().IsInRegion(parentLocalPoint)) {
288         // Calculates the local point location and coordinate offset in this node.
289         const auto localPoint = parentLocalPoint - GetPaintRect().GetOffset();
290         const auto coordinateOffset = globalPoint - localPoint;
291         globalPoint_ = globalPoint;
292         OnTouchTestHit(coordinateOffset, touchRestrict, result);
293         return true;
294     } else {
295         if (IsValidBubbleBox() && bubbleController_ && bubbleController_->IsRunning()) {
296             bubbleController_->Finish();
297         }
298     }
299     return false;
300 }
301 
OnTouchTestHit(const Offset & coordinateOffset,const TouchRestrict & touchRestrict,TouchTestResult & result)302 void RenderIndexer::OnTouchTestHit(
303     const Offset& coordinateOffset, const TouchRestrict& touchRestrict, TouchTestResult& result)
304 {
305     if (dragRecognizer_) {
306         dragRecognizer_->SetCoordinateOffset(coordinateOffset);
307         result.emplace_back(dragRecognizer_);
308     }
309 
310     if (touchRecognizer_) {
311         touchRecognizer_->SetCoordinateOffset(coordinateOffset);
312         result.emplace_back(touchRecognizer_);
313     }
314 }
315 
HandleTouched(const Offset & touchPosition)316 void RenderIndexer::HandleTouched(const Offset& touchPosition)
317 {
318     int32_t index = GetTouchedItemIndex(touchPosition);
319     if (index > INVALID_INDEX) {
320         MoveList(index);
321     }
322 }
323 
GetTouchedItemIndex(const Offset & touchPosition)324 int32_t RenderIndexer::GetTouchedItemIndex(const Offset& touchPosition)
325 {
326     double position = touchPosition.GetY();
327     if (position < paddingY_) {
328         return INVALID_INDEX;
329     }
330 
331     if (NearZero(itemSizeRender_)) {
332         LOGE("[indexer] Invalid Item size:%{public}lf", itemSizeRender_);
333         return INVALID_INDEX;
334     }
335 
336     int32_t index = static_cast<int32_t>((position - paddingY_) / itemSizeRender_);
337     LOGI("[indexer] HandleTouched section index:%{public}d", index);
338     return GetItemIndex(index);
339 }
340 
UpdateBubbleText()341 void RenderIndexer::UpdateBubbleText()
342 {
343     if (IsValidBubbleBox() && !bubbleBox_->GetChildren().empty()) {
344         auto text = AceType::DynamicCast<RenderText>(bubbleBox_->GetChildren().front());
345         auto item = GetSpecificItem(focusedItem_);
346         if (bubbleText_ && text && item) {
347             bubbleText_->SetData(item->GetSectionText());
348             text->Update(bubbleText_);
349             text->PerformLayout();
350             BeginBubbleAnimation();
351         }
352     }
353 }
354 
BuildBubbleAnimation()355 void RenderIndexer::BuildBubbleAnimation()
356 {
357     if (!IsValidBubbleBox()) {
358         return;
359     }
360     if (!bubbleController_) {
361         bubbleController_ = CREATE_ANIMATOR(GetContext());
362     }
363     bubbleController_->ClearInterpolators();
364     bubbleController_->ClearAllListeners();
365     auto weak = AceType::WeakClaim(this);
366     bubbleController_->AddStopListener([weak]() {
367         auto indexer = weak.Upgrade();
368         if (indexer && indexer->IsValidBubbleBox()) {
369             indexer->bubbleDisplay_->UpdateOpacity(ZERO_OPACITY);
370         }
371     });
372 
373     // build and start animation
374     auto animation = AceType::MakeRefPtr<KeyframeAnimation<uint8_t>>();
375     auto startFrame = AceType::MakeRefPtr<Keyframe<uint8_t>>(KEYFRAME_BEGIN, ZERO_OPACITY);
376     auto midFrame = AceType::MakeRefPtr<Keyframe<uint8_t>>(KEYFRAME_HALF, DEFAULT_OPACITY);
377     auto endFrame = AceType::MakeRefPtr<Keyframe<uint8_t>>(KEYFRAME_END, ZERO_OPACITY);
378     midFrame->SetCurve(Curves::DECELE);
379     endFrame->SetCurve(Curves::DECELE);
380     animation->AddKeyframe(startFrame);
381     animation->AddKeyframe(midFrame);
382     animation->AddKeyframe(endFrame);
383     animation->AddListener([weak](uint8_t value) {
384         auto indexer = weak.Upgrade();
385         if (indexer && indexer->IsValidBubbleBox()) {
386             indexer->bubbleDisplay_->UpdateOpacity(value);
387         }
388     });
389 
390     bubbleController_->AddInterpolator(animation);
391     bubbleController_->SetDuration(INDEXER_BUBBLE_ANIMATION_DURATION);
392 }
393 
BeginBubbleAnimation()394 void RenderIndexer::BeginBubbleAnimation()
395 {
396     if (!IsValidBubbleBox()) {
397         return;
398     }
399     if (!bubbleController_) {
400         BuildBubbleAnimation();
401     }
402     if (bubbleController_->IsRunning()) {
403         bubbleController_->Finish();
404     }
405     bubbleController_->Play();
406 }
407 
IsValidBubbleBox()408 bool RenderIndexer::IsValidBubbleBox()
409 {
410     if (!bubbleEnabled_ || GetChildren().empty()) {
411         return false;
412     }
413     if (!bubbleDisplay_) {
414         bubbleDisplay_ = AceType::DynamicCast<RenderDisplay>(GetChildren().front());
415         if (!bubbleDisplay_ || bubbleDisplay_->GetChildren().empty()) {
416             return false;
417         }
418         bubbleBox_ = AceType::DynamicCast<RenderBox>(bubbleDisplay_->GetChildren().front());
419         if (!bubbleBox_) {
420             return false;
421         }
422     }
423     return true;
424 }
425 
GetItemIndex(int32_t index)426 int32_t RenderIndexer::GetItemIndex(int32_t index)
427 {
428     int32_t itemIndexInList = INVALID_INDEX;
429     RefPtr<RenderNode> itemNode;
430     auto iter = items_.begin();
431     std::advance(iter, index);
432     if (iter == items_.end()) {
433         itemNode = items_.back();
434     } else {
435         itemNode = *iter;
436     }
437     RefPtr<RenderIndexerItem> indexerItem = AceType::DynamicCast<RenderIndexerItem>(itemNode);
438     if (indexerItem && indexerItem->GetKeyCount() > 0) {
439         itemIndexInList = indexerItem->GetSectionIndex();
440     }
441     LOGI("[indexer] GetItemIndex index:%{public}d indexInList:%{public}d", index, itemIndexInList);
442     return itemIndexInList;
443 }
444 
MoveSection(int32_t index)445 void RenderIndexer::MoveSection(int32_t index)
446 {
447     HandleListMoveItems(index);
448     lastHeadIndex_ = index;
449     int32_t curSection = 0;
450     int32_t needReverse = 0;
451     for (auto itemNode : items_) {
452         RefPtr<RenderIndexerItem> item = AceType::DynamicCast<RenderIndexerItem>(itemNode);
453         if (item && item->GetKeyCount() > 0) {
454             int32_t nextIndex = item->GetSectionIndex();
455             if (index < nextIndex) {
456                 curSection -= needReverse; // back to previous valid seciton
457                 break;
458             }
459             if (index == nextIndex) {
460                 break;
461             }
462             needReverse = 0;
463         }
464 
465         ++curSection;
466         ++needReverse;
467     }
468 
469     if (curSection >= itemCount_ - 1) {
470         curSection = itemCount_ - 2; // skip collapse item
471     }
472 
473     if (focusedItem_ == curSection) {
474         return;
475     }
476 
477     RefPtr<RenderIndexerItem> curItem = GetSpecificItem(curSection);
478     if (!curItem || !NeedProcess(curItem) || curItem->GetKeyCount() <= 0) {
479         LOGW("[indexer] invalid indexer item");
480         return;
481     }
482 
483     // change to correct index
484     BeginFocusAnimation(focusedItem_, curSection);
485     curItem->SetClicked(true); // Make cur clicked item focus.
486     UpdateCurrentSectionItem(curSection);
487 
488     // Make pre focused item blur.
489     RefPtr<RenderIndexerItem> preItem = GetSpecificItem(focusedItem_);
490     if (preItem) {
491         preItem->SetClicked(false);
492     }
493     focusedItem_ = curSection;
494     isInitFocus_ = false;
495     UpdateBubbleText();
496 }
497 
HandleFocusAnimation(const Size & size,const Offset & offset)498 void RenderIndexer::HandleFocusAnimation(const Size& size, const Offset& offset)
499 {
500     auto context = context_.Upgrade();
501     if (!context) {
502         LOGE("Pipeline context upgrade fail!");
503         return;
504     }
505     if (!context->GetRenderFocusAnimation()) {
506         LOGE("focusAnimation is null!");
507         return;
508     }
509 
510     double focusPadding = NormalizeToPx(FOCUS_PADDING);
511     // four focusPadding.
512     context->ShowFocusAnimation(
513         RRect::MakeRRect(Rect(Offset(), size - Size(focusPadding, focusPadding) * 2), focusPadding, focusPadding),
514         Color::BLUE, offset + Offset(focusPadding, focusPadding));
515 }
516 
MoveSectionWithIndexer(int32_t curSection)517 void RenderIndexer::MoveSectionWithIndexer(int32_t curSection)
518 {
519     if (focusedItem_ == curSection) {
520         return;
521     }
522 
523     RefPtr<RenderIndexerItem> curItem = GetSpecificItem(curSection);
524     if (!curItem || !NeedProcess(curItem) || curItem->GetKeyCount() <= 0) {
525         LOGW("[indexer] invalid indexer item");
526         return;
527     }
528 
529     // change to correct index
530     BeginFocusAnimation(focusedItem_, curSection);
531     curItem->SetClicked(true); // Make cur clicked item focus.
532     UpdateCurrentSectionItem(curSection);
533 
534     // Make pre focused item blur.
535     RefPtr<RenderIndexerItem> preItem = GetSpecificItem(focusedItem_);
536     if (preItem) {
537         preItem->SetClicked(false);
538     }
539     focusedItem_ = curSection;
540     isInitFocus_ = false;
541     MoveList(curItem->GetSectionIndex());
542     UpdateBubbleText();
543 }
544 
MoveList(int32_t index)545 void RenderIndexer::MoveList(int32_t index)
546 {
547     if (index < 0) {
548         LOGE("[indexer] invalid item indexer");
549         return;
550     }
551     if (!controller_) {
552         LOGE("[indexer] position controller is null.");
553         return;
554     }
555     // to avoid sticky item overlap, reference: RenderList::CalculateItemPosition
556     int32_t indexToJump = circleMode_ ? index : index + 1;
557     lastHeadIndex_ = indexToJump;
558     LOGI("[indexer] MoveList Jump to index[%{public}d], indexToJump[%{public}d]", index, indexToJump);
559     controller_->ScrollToIndex(indexToJump, false, ScrollAlign::START, std::nullopt);
560 }
561 
GetSpecificItem(int32_t index)562 RefPtr<RenderIndexerItem> RenderIndexer::GetSpecificItem(int32_t index)
563 {
564     auto iter = items_.begin();
565     std::advance(iter, index);
566     if (iter != items_.end()) {
567         return AceType::DynamicCast<RenderIndexerItem>(*iter);
568     }
569     return nullptr;
570 }
571 
572 } // namespace OHOS::Ace
573