1 /*
2  * Copyright (c) 2021 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_circle.h"
17 
18 namespace OHOS::Ace {
19 
Create()20 RefPtr<RenderNode> RenderIndexerCircle::Create()
21 {
22     LOGI("[indexer] Create RenderIndexerCircle");
23     return AceType::MakeRefPtr<RenderIndexerCircle>();
24 }
25 
Update(const RefPtr<Component> & component)26 void RenderIndexerCircle::Update(const RefPtr<Component>& component)
27 {
28     RenderIndexer::Update(component);
29 
30     auto context = GetContext().Upgrade();
31     if (context) {
32         hotRgnSize_ = context->NormalizeToPx(Dimension(hotRgnSize_, DimensionUnit::VP));
33     }
34     if (itemCount_ <= INDEXER_COLLAPSE_ITEM_COUNT) {
35         curStatus_ = IndexerItemStatus::COLLAPSE;
36         collapseItemCount_ = itemCount_;
37     }
38 
39     RefPtr<IndexerComponent> componentIndex = AceType::DynamicCast<IndexerComponent>(component);
40     if (componentIndex) {
41         itemMaxCount_ = std::clamp(componentIndex->GetMaxShowCount(), 1, INDEXER_ITEM_MAX_COUNT + 1);
42         hasCollapseItem_ = componentIndex->HasCollapseItem();
43         perItemExtent_ = DOUBLE * M_PI / itemMaxCount_;
44         indexerChangeEvent_ =
45             AceAsyncEvent<void(const std::string&)>::Create(componentIndex->GetIndexerChange(), context_);
46     }
47 
48     LOGI("[indexer] hotRgnSize:%{public}lf, CollapseCount:%{public}d, TotalCount:%{public}d, perItemExtent:%{public}lf",
49         hotRgnSize_, collapseItemCount_, itemCount_, perItemExtent_);
50 }
51 
PerformLayout()52 void RenderIndexerCircle::PerformLayout()
53 {
54     if (GetChildren().empty()) {
55         return;
56     }
57     UpdateItems();
58     if (isInitFocus_) {
59         InitFocusedItem();
60     }
61     if (IsValidArc()) {
62         arc_->SetStartAngle(arcHeadPosition_);
63         arc_->SetSweepAngle(arcTailPosition_ - arcHeadPosition_);
64     }
65     const LayoutParam& layoutSetByParent = GetLayoutParam();
66     LayoutParam childrenLayout = layoutSetByParent;
67     childrenLayout.SetMinSize(Size(0.0, 0.0));
68     for (const auto& item : GetChildren()) {
69         item->Layout(childrenLayout);
70     }
71 
72     // calculate self and children size
73     Size sizeMax = GetChildren().front()->GetLayoutSize(); // get arc layout size first
74     SetLayoutSize(sizeMax);
75 
76     if (items_.empty()) {
77         LOGE("[indexer] invalid children count:%{public}u", static_cast<int32_t>(GetChildren().size()));
78         return;
79     }
80 
81     // check whether the layout size was valid
82     outerRadius_ = sizeMax.Width() * HALF;
83     if (LessOrEqual(outerRadius_, itemSize_ * DOUBLE)) {
84         LOGE("[indexer] invalid outerRadius_:%{public}lf, itemSize_:%{public}lf", outerRadius_, itemSize_);
85         return;
86     }
87 
88     if (curStatus_ != IndexerItemStatus::EXPAND && itemCount_ > collapseItemCount_) {
89         return;
90     }
91 
92     ResetItemsPosition();
93     InitBubbleBox(sizeMax);
94     InitIndicatorBox();
95 }
96 
SetItemPosition(const RefPtr<RenderNode> & item,int32_t positionIndex)97 void RenderIndexerCircle::SetItemPosition(const RefPtr<RenderNode>& item, int32_t positionIndex)
98 {
99     if (positionIndex >= 0 && positionIndex <= INDEXER_ITEM_MAX_COUNT) {
100         double itemRadius = outerRadius_ - itemSize_ * HALF;
101         Offset position;
102         position.SetX(itemRadius + itemRadius * itemInfo_[positionIndex][TABLE_POSITION_X]);
103         position.SetY(itemRadius + itemRadius * itemInfo_[positionIndex][TABLE_POSITION_Y]);
104         item->SetPosition(position);
105     }
106 }
107 
SetItemPosition(const RefPtr<RenderNode> & item,double angle)108 void RenderIndexerCircle::SetItemPosition(const RefPtr<RenderNode>& item, double angle)
109 {
110     double itemRadius = outerRadius_ - itemSize_ * HALF;
111     Offset position;
112     position.SetX(itemRadius + itemRadius * sin(M_PI_2 + angle));
113     position.SetY(itemRadius - itemRadius * cos(M_PI_2 + angle));
114     item->SetPosition(position);
115 }
116 
ResetItemsPosition()117 void RenderIndexerCircle::ResetItemsPosition()
118 {
119     if (!IsValidArc() || GetChildren().empty()) {
120         return;
121     }
122     arcHeadOffset_ = 0.0;
123     int32_t count = 0;
124     for (auto item : items_) {
125         if (!NeedProcess(item)) {
126             item->SetVisible(false);
127             continue;
128         }
129         SetItemPosition(item, count);
130         item->SetVisible(true);
131         ++count;
132     }
133 
134     arcMaxLen_ = count == itemMaxCount_ ? M_PI * DOUBLE : (count - 1) * perItemExtent_;
135     if (curStatus_ != IndexerItemStatus::ANIMATION) {
136         arc_->SetSweepAngle(arcMaxLen_);
137     }
138     if (!hasCollapseItem_) {
139         return;
140     }
141 
142     currentCount_ = count - 1;
143     if (currentCount_ <= collapseItemCount_) {
144         auto collapse = GetChildren().back();
145         if (collapse) {
146             collapse->SetVisible(false);
147         }
148         arc_->SetSweepAngle((currentCount_ - 1) * perItemExtent_);
149         return;
150     }
151 
152     SetCollapseItemPosition(itemInfo_[itemMaxCount_ - 1][TABLE_ANGLE]);
153     if (curStatus_ != IndexerItemStatus::ANIMATION) {
154         arcTailPosition_ = arcMaxLen_ + arcHeadPosition_;
155         SetCollapseItemPosition(arcTailPosition_);
156     }
157 }
158 
TouchTest(const Point & globalPoint,const Point & parentLocalPoint,const TouchRestrict & touchRestrict,TouchTestResult & result)159 bool RenderIndexerCircle::TouchTest(const Point& globalPoint, const Point& parentLocalPoint,
160     const TouchRestrict& touchRestrict, TouchTestResult& result)
161 {
162     if (!IsValidArc()) {
163         LOGE("[indexer] TouchTest arc render node is invalid");
164         return false;
165     }
166     if (GetDisableTouchEvent() || disabled_) {
167         return false;
168     }
169     // Since the paintRect is relative to parent, use parent local point to perform touch test.
170     if (arc_->IsInRegion(parentLocalPoint - GetPaintRect().GetOffset())) {
171         // Calculates the local point location and coordinate offset in this node.
172         const auto localPoint = parentLocalPoint - GetPaintRect().GetOffset();
173         const auto coordinateOffset = globalPoint - localPoint;
174         globalPoint_ = globalPoint;
175         OnTouchTestHit(coordinateOffset, touchRestrict, result);
176         return true;
177     } else {
178         if (IsValidBubbleBox() && bubbleController_ && bubbleController_->IsRunning()) {
179             bubbleController_->Finish();
180         }
181     }
182     return true;
183 }
184 
HandleTouchDown(const TouchEventInfo & info)185 void RenderIndexerCircle::HandleTouchDown(const TouchEventInfo& info)
186 {
187     LOGI("[indexer] HandleTouchDown.");
188     if (curStatus_ == IndexerItemStatus::ANIMATION) {
189         return;
190     }
191     if (info.GetTouches().empty()) {
192         return;
193     }
194     Offset position = info.GetTouches().front().GetLocalLocation();
195 
196     if (CollapseItemHitTest(position)) {
197         curStatus_ == IndexerItemStatus::EXPAND ? CollapseItems() : ExpandItems();
198         return;
199     }
200 
201     touching_ = true;
202     if (curStatus_ == IndexerItemStatus::EXPAND) {
203         int32_t item = GetNearestItem(position, false);
204         if (item <= INVALID_INDEX || item >= itemCount_ - 1) {
205             LOGE("[indexer] item index %{public}d is not valid", item);
206             return;
207         }
208 
209         if (ChangeLanguage(item)) {
210             LOGI("[indexer] : change to show another language");
211             touching_ = false;
212             return;
213         }
214         SetItemsFocused(item);
215     } else {
216         TouchOnCollapseArc(position);
217     }
218 }
219 
NeedChangeIndexer(int32_t index)220 bool RenderIndexerCircle::NeedChangeIndexer(int32_t index)
221 {
222     if (curStatus_ != IndexerItemStatus::EXPAND) {
223         return false;
224     }
225     auto indexerItem = GetSpecificItem(index);
226     if (!indexerItem) {
227         return false;
228     }
229 
230     // skip # and *
231     if (indexerItem->GetSectionText() == StringUtils::Str16ToStr8(INDEXER_STR_SHARP) ||
232         indexerItem->GetSectionText() == StringUtils::Str16ToStr8(INDEXER_STR_COLLECT)) {
233         return false;
234     }
235     return indexerItem->GetItemType() != curItemType_;
236 }
237 
HandleTouchUp(const TouchEventInfo & info)238 void RenderIndexerCircle::HandleTouchUp(const TouchEventInfo& info)
239 {
240     LOGI("[indexer] HandleTouchUp.");
241     curHitItem_ = -1;
242     touching_ = false;
243 }
244 
HandleTouchMove(const TouchEventInfo & info)245 void RenderIndexerCircle::HandleTouchMove(const TouchEventInfo& info)
246 {
247     if (!touching_) {
248         return;
249     }
250     if (info.GetTouches().empty()) {
251         return;
252     }
253     Offset position = info.GetTouches().front().GetLocalLocation();
254     if (curStatus_ == IndexerItemStatus::EXPAND) {
255         int32_t curItem = GetNearestItem(position, true);
256         if (curItem == INVALID_INDEX || curItem >= itemCount_ - 1) {
257             return;
258         }
259         if (curItem != curHitItem_) {
260             curHitItem_ = curItem;
261             SetItemsFocused(curItem);
262         }
263     } else if (curStatus_ == IndexerItemStatus::COLLAPSE) {
264         TouchOnCollapseArc(position);
265     } else {
266         return;
267     }
268 }
269 
GetTouchedItemIndex(const Offset & touchPosition)270 int32_t RenderIndexerCircle::GetTouchedItemIndex(const Offset& touchPosition)
271 {
272     if (NearZero(itemSize_)) {
273         LOGE("[indexer] Invalid Item size:%{public}lf", itemSize_);
274         return INVALID_INDEX;
275     }
276 
277     double touchAngle = atan2(touchPosition.GetY() - outerRadius_, touchPosition.GetX() - outerRadius_);
278     if (touchPosition.GetY() - outerRadius_ < 0.0) {
279         touchAngle += DOUBLE * M_PI;
280     }
281     touchAngle += arcHeadPosition_;
282 
283     int32_t itemIndexInIndexer = static_cast<int32_t>(touchAngle / perItemExtent_);
284     LOGI("[indexer] HandleTouched section index:%{public}d, touchAngle:%{public}lf", itemIndexInIndexer, touchAngle);
285     return GetItemIndex(itemIndexInIndexer);
286 }
287 
InitIndexTable()288 void RenderIndexerCircle::InitIndexTable()
289 {
290     // init information table
291     LOGI("[indexer] InitIndexTable Count:%{public}d, MaxCount:%{public}d", itemCount_, itemMaxCount_);
292     for (int32_t i = 0; i < itemMaxCount_; ++i) {
293         itemInfo_[i][TABLE_ANGLE] = INDEXER_ARC_BEGIN + i * perItemExtent_;
294         itemInfo_[i][TABLE_POSITION_X] = sin(M_PI_2 + itemInfo_[i][TABLE_ANGLE]);
295         itemInfo_[i][TABLE_POSITION_Y] = -cos(M_PI_2 + itemInfo_[i][TABLE_ANGLE]);
296     }
297 }
298 
HandleListMoveItems(int32_t curHeadIndex)299 void RenderIndexerCircle::HandleListMoveItems(int32_t curHeadIndex)
300 {
301     if (curStatus_ != IndexerItemStatus::EXPAND) {
302         return;
303     }
304     if (lastHeadIndex_ != curHeadIndex) {
305         CollapseItems();
306     }
307 }
308 
InitBubbleBox(const Size & size)309 void RenderIndexerCircle::InitBubbleBox(const Size& size)
310 {
311     if (IsValidBubbleBox()) {
312         Offset bubblePosition;
313         bubblePosition.SetX(
314             size.Width() * HALF - NormalizeToPx(Dimension(BUBBLE_BOX_SIZE_CIRCLE * HALF, DimensionUnit::VP)));
315         bubblePosition.SetY(
316             size.Height() * HALF * HALF - NormalizeToPx(Dimension(BUBBLE_BOX_SIZE_CIRCLE * HALF, DimensionUnit::VP)));
317         bubbleDisplay_->SetPosition(bubblePosition);
318     }
319 }
320 
InitIndicatorBox()321 void RenderIndexerCircle::InitIndicatorBox()
322 {
323     if (IsValidIndicatorBox()) {
324         Offset boxPosition;
325         double angle = INDEXER_ARC_BEGIN + (collapseItemCount_ - 1) * perItemExtent_;
326         double itemRadius = outerRadius_ - itemSize_ * HALF;
327         boxPosition.SetX(itemRadius + itemRadius * sin(M_PI_2 + angle));
328         boxPosition.SetY(itemRadius - itemRadius * cos(M_PI_2 + angle));
329         indicatorBox_->SetPosition(boxPosition);
330         indicatorBox_->SetColor(Color::TRANSPARENT, false);
331         itemColorEnabled_ = true;
332     }
333 }
334 
IsValidIndicatorBox()335 bool RenderIndexerCircle::IsValidIndicatorBox()
336 {
337     if (!indicatorBox_ && !GetChildren().empty()) {
338         auto iter = GetChildren().begin();
339         std::advance(iter, 1);
340         indicatorBox_ = iter == GetChildren().end() ? nullptr : AceType::DynamicCast<RenderBox>(*iter);
341         if (!indicatorBox_) {
342             LOGE("[indexer] IsValidIndicatorBox. indicatorBox_ ptr is NULL.");
343             return false;
344         }
345     }
346     return true;
347 }
348 
IsValidBubbleBox()349 bool RenderIndexerCircle::IsValidBubbleBox()
350 {
351     if (!bubbleEnabled_ || GetChildren().empty()) {
352         return false;
353     }
354     if (!bubbleDisplay_) {
355         auto iter = GetChildren().begin();
356         std::advance(iter, 2);
357         bubbleDisplay_ = iter == GetChildren().end() ? nullptr : AceType::DynamicCast<RenderDisplay>(*iter);
358         if (!bubbleDisplay_ || bubbleDisplay_->GetChildren().empty()) {
359             LOGE("[indexer] IsValidBubbleBox. bubbleBox_ ptr is NULL.");
360             return false;
361         }
362         bubbleBox_ = AceType::DynamicCast<RenderBox>(bubbleDisplay_->GetChildren().front());
363         if (!bubbleBox_) {
364             return false;
365         }
366     }
367     return true;
368 }
369 
IsValidArc()370 bool RenderIndexerCircle::IsValidArc()
371 {
372     if (!arc_) {
373         arc_ = GetChildren().empty() ? nullptr : AceType::DynamicCast<RenderArc>(GetChildren().front());
374         if (!arc_) {
375             LOGE("[indexer] IsValidArc. Arc ptr is NULL.");
376             return false;
377         }
378     }
379     return true;
380 }
381 
UpdateCurrentSectionItem(int32_t curSection)382 void RenderIndexerCircle::UpdateCurrentSectionItem(int32_t curSection)
383 {
384     if (curStatus_ != IndexerItemStatus::COLLAPSE) {
385         return;
386     }
387 
388     // calculate drag angle
389     int32_t index = GetPositionItemIndex(curSection);
390     double offsetAngle = -(index + 1 - collapseItemCount_) * perItemExtent_;
391     offsetAngle = std::min(offsetAngle, 0.0);
392     // make indicator box visible and item background color disabled
393     if (LessNotEqual(offsetAngle, 0.0)) {
394         if (focusController_ && focusController_->IsRunning()) {
395             focusController_->Finish();
396         }
397         if (IsValidIndicatorBox()) {
398             indicatorBox_->SetColor(Color(INDEXER_CIRCLE_ACTIVE_BG_COLOR), false);
399             itemColorEnabled_ = false;
400         }
401     }
402 
403     if (!NearEqual(offsetAngle, arcHeadOffset_)) {
404         BeginCollapseScrollAnimation(arcHeadOffset_, offsetAngle);
405     }
406 }
407 
CollapseItems()408 void RenderIndexerCircle::CollapseItems()
409 {
410     LOGI("[indexer] CollapseItems");
411     if (currentCount_ <= collapseItemCount_) {
412         return;
413     }
414 
415     if (collapseScrollController_ && collapseScrollController_->IsRunning()) {
416         itemColorEnabled_ = true;
417         collapseScrollController_->Finish();
418     }
419 
420     // finish focus change animation first
421     if (focusController_ && focusController_->IsRunning()) {
422         focusController_->Finish();
423     }
424     BeginArcAnimation(true);
425 }
426 
ExpandItems()427 void RenderIndexerCircle::ExpandItems()
428 {
429     LOGI("[indexer] ExpandItems");
430     if (IsValidIndicatorBox()) {
431         indicatorBox_->SetColor(Color::TRANSPARENT, false);
432         itemColorEnabled_ = true;
433         UpdateItemBackground(DEFAULT_OPACITY_IN_PERCENT, focusedItem_);
434     }
435     BeginArcAnimation(false);
436 }
437 
BuildArcAnimation()438 void RenderIndexerCircle::BuildArcAnimation()
439 {
440     if (!IsValidArc()) {
441         LOGE("[indexer] BuildArcAnimation arc render node is invalid");
442         return;
443     }
444     if (!collapseController_) {
445         collapseController_ = CREATE_ANIMATOR(GetContext());
446     } else {
447         collapseController_->ClearInterpolators();
448         collapseController_->ClearAllListeners();
449     }
450 
451     double collapseTail = arcHeadPosition_ + collapseItemCount_ * perItemExtent_;
452     auto tailStartFrame = AceType::MakeRefPtr<Keyframe<double>>(KEYFRAME_BEGIN, collapseTail);
453     auto tailEndFrame = AceType::MakeRefPtr<Keyframe<double>>(KEYFRAME_END, arcHeadPosition_ + arcMaxLen_);
454     tailEndFrame->SetCurve(Curves::FAST_OUT_SLOW_IN);
455 
456     auto tailInterpolator = AceType::MakeRefPtr<KeyframeAnimation<double>>();
457     tailInterpolator->AddKeyframe(tailStartFrame);
458     tailInterpolator->AddKeyframe(tailEndFrame);
459     tailInterpolator->AddListener([weak = AceType::WeakClaim(this)](double value) {
460         auto indexer = weak.Upgrade();
461         if (indexer) {
462             indexer->UpdateArcTail(value);
463         }
464     });
465 
466     collapseController_->AddInterpolator(tailInterpolator);
467     auto weak = AceType::WeakClaim(this);
468     collapseController_->AddStopListener([weak]() {
469         auto indexer = weak.Upgrade();
470         if (indexer) {
471             indexer->HandleArcAnimationStop();
472         }
473     });
474     collapseController_->AddStartListener([weak]() {
475         auto indexer = weak.Upgrade();
476         if (indexer) {
477             indexer->HandleArcAnimationStart();
478         }
479     });
480     collapseController_->SetDuration(INDEXER_ANIMATION_DURATION);
481     collapseController_->SetFillMode(FillMode::FORWARDS);
482 }
483 
BeginArcAnimation(bool collapse)484 void RenderIndexerCircle::BeginArcAnimation(bool collapse)
485 {
486     if (!IsValidArc()) {
487         LOGE("[indexer] BeginArcAnimation arc render node is invalid");
488         return;
489     }
490     if (!collapseController_) {
491         BuildArcAnimation();
492     }
493     if (collapseController_->IsRunning()) {
494         collapseController_->Finish();
495     }
496     collapse ? collapseController_->Backward() : collapseController_->Forward();
497 }
498 
HandleArcAnimationStart()499 void RenderIndexerCircle::HandleArcAnimationStart()
500 {
501     nextStatus_ = curStatus_ == IndexerItemStatus::EXPAND ? IndexerItemStatus::COLLAPSE : IndexerItemStatus::EXPAND;
502     curStatus_ = IndexerItemStatus::ANIMATION;
503 }
504 
HandleArcAnimationStop()505 void RenderIndexerCircle::HandleArcAnimationStop()
506 {
507     curStatus_ = nextStatus_;
508 }
509 
BeginCollapseScrollAnimation(double originOffset,double targetOffset)510 void RenderIndexerCircle::BeginCollapseScrollAnimation(double originOffset, double targetOffset)
511 {
512     if (!collapseScrollController_) {
513         collapseScrollController_ = CREATE_ANIMATOR(GetContext());
514     }
515 
516     if (collapseScrollController_->IsRunning()) {
517         collapseScrollController_->Finish();
518     }
519     collapseScrollController_->ClearInterpolators();
520     collapseScrollController_->ClearAllListeners();
521     collapseScrollController_->AddStopListener([weak = AceType::WeakClaim(this)]() {
522         auto indexer = weak.Upgrade();
523         if (indexer) {
524             indexer->HandleCollapseScrollAnimationStop();
525         }
526     });
527 
528     InitCollapseScrollInterpolator(originOffset, targetOffset);
529     collapseScrollController_->SetDuration(INDEXER_ANIMATION_DURATION);
530     collapseScrollController_->Play();
531 }
532 
HandleCollapseScrollAnimationStop()533 void RenderIndexerCircle::HandleCollapseScrollAnimationStop()
534 {
535     if (IsValidIndicatorBox() && NearEqual(0.0, arcHeadOffset_)) {
536         indicatorBox_->SetColor(Color::TRANSPARENT, false);
537         itemColorEnabled_ = true;
538         UpdateItemBackground(DEFAULT_OPACITY_IN_PERCENT, focusedItem_);
539     }
540 }
541 
BeginFocusAnimation(int32_t originIndex,int32_t targetIndex)542 void RenderIndexerCircle::BeginFocusAnimation(int32_t originIndex, int32_t targetIndex)
543 {
544     if (!NearEqual(0.0, arcHeadOffset_) && !itemColorEnabled_) {
545         return;
546     }
547 
548     // stop and reset animator
549     if (!focusController_) {
550         focusController_ = CREATE_ANIMATOR(GetContext());
551     }
552 
553     if (focusController_->IsRunning()) {
554         focusController_->Finish();
555         SetIndexerItemStatus(false, originIndex, targetIndex);
556     }
557     focusController_->ClearInterpolators();
558     focusController_->ClearAllListeners();
559 
560     SetIndexerItemStatus(true, originIndex, targetIndex);
561 
562     auto weak = AceType::WeakClaim(this);
563     focusController_->AddStopListener([weak, originIndex, targetIndex]() {
564         auto indexer = weak.Upgrade();
565         if (indexer) {
566             indexer->SetIndexerItemStatus(false, originIndex, targetIndex);
567         }
568     });
569 
570     InitFocusInterpolator(ZERO_OPACITY_IN_PERCENT, DEFAULT_OPACITY_IN_PERCENT, originIndex, targetIndex);
571     focusController_->SetDuration(INDEXER_ANIMATION_DURATION);
572     focusController_->Play();
573 }
574 
SetIndexerItemStatus(bool inAnimation,int32_t originIndex,int32_t targetIndex)575 void RenderIndexerCircle::SetIndexerItemStatus(bool inAnimation, int32_t originIndex, int32_t targetIndex)
576 {
577     RefPtr<RenderIndexerItem> focusItem = GetSpecificItem(targetIndex);
578     RefPtr<RenderIndexerItem> preFocusItem = GetSpecificItem(originIndex);
579     if (!focusItem || !preFocusItem) {
580         return;
581     }
582     focusItem->MarkItemInAnimation(inAnimation);
583     preFocusItem->MarkItemInAnimation(inAnimation);
584 
585     if (!inAnimation) {
586         focusItem->UpdateItemStyle();
587         preFocusItem->UpdateItemStyle();
588     }
589 }
590 
UpdateItemsPosition(double arcOffset)591 void RenderIndexerCircle::UpdateItemsPosition(double arcOffset)
592 {
593     if (curStatus_ == IndexerItemStatus::EXPAND) {
594         return;
595     }
596 
597     RotateItems(arcOffset);
598     SetItemsVisible(arcHeadPosition_ - arcOffset, arcTailPosition_ - perItemExtent_ - arcOffset);
599     SetCollapseItemPosition(itemInfo_[collapseItemCount_][TABLE_ANGLE]);
600     MarkNeedRender();
601 }
602 
UpdateItemBackground(double value,int32_t originIndex,int32_t targetIndex)603 void RenderIndexerCircle::UpdateItemBackground(double value, int32_t originIndex, int32_t targetIndex)
604 {
605     UpdateItemBackground(value, targetIndex);
606     UpdateItemBackground(DEFAULT_OPACITY_IN_PERCENT - value, originIndex);
607 }
608 
UpdateItemBackground(double value,int32_t index)609 void RenderIndexerCircle::UpdateItemBackground(double value, int32_t index)
610 {
611     RefPtr<RenderIndexerItem> item = GetSpecificItem(index);
612     if (item) {
613         item->UpdateItemBackground(Color(INDEXER_CIRCLE_ACTIVE_BG_COLOR).BlendOpacity(value));
614     }
615 }
616 
InitCollapseScrollInterpolator(double origin,double target)617 void RenderIndexerCircle::InitCollapseScrollInterpolator(double origin, double target)
618 {
619     auto startFrame = AceType::MakeRefPtr<Keyframe<double>>(KEYFRAME_BEGIN, origin);
620     auto endFrame = AceType::MakeRefPtr<Keyframe<double>>(KEYFRAME_END, target);
621     endFrame->SetCurve(Curves::FAST_OUT_SLOW_IN);
622 
623     auto animation = AceType::MakeRefPtr<KeyframeAnimation<double>>();
624     animation->AddKeyframe(startFrame);
625     animation->AddKeyframe(endFrame);
626     animation->AddListener([weak = AceType::WeakClaim(this)](double value) {
627         auto indexer = weak.Upgrade();
628         if (indexer) {
629             indexer->UpdateItemsPosition(value);
630         }
631     });
632 
633     collapseScrollController_->AddInterpolator(animation);
634 }
635 
InitFocusInterpolator(double origin,double target,int32_t originIndex,int32_t targetIndex)636 void RenderIndexerCircle::InitFocusInterpolator(double origin, double target, int32_t originIndex, int32_t targetIndex)
637 {
638     auto startFrame = AceType::MakeRefPtr<Keyframe<double>>(KEYFRAME_BEGIN, origin);
639     auto endFrame = AceType::MakeRefPtr<Keyframe<double>>(KEYFRAME_END, target);
640     endFrame->SetCurve(Curves::FAST_OUT_SLOW_IN);
641 
642     auto weak = AceType::WeakClaim(this);
643     auto animation = AceType::MakeRefPtr<KeyframeAnimation<double>>();
644     animation->AddKeyframe(startFrame);
645     animation->AddKeyframe(endFrame);
646     animation->AddListener([weak, originIndex, targetIndex](double value) {
647         auto indexer = weak.Upgrade();
648         if (indexer) {
649             indexer->UpdateItemBackground(value, originIndex, targetIndex);
650         }
651     });
652 
653     focusController_->AddInterpolator(animation);
654 }
655 
UpdateArcTail(double value)656 void RenderIndexerCircle::UpdateArcTail(double value)
657 {
658     if (!IsValidArc()) {
659         LOGE("[indexer] UpdateArcTail arc render node is invalid");
660         return;
661     }
662     if (focusedItem_ >= itemCount_ - 1) {
663         LOGE("[indexer] UpdateArcTail. Invalid focusedItem:%{public}d", focusedItem_);
664         return;
665     }
666 
667     arcTailPosition_ = value;
668     arc_->SetSweepAngle(arcTailPosition_ - arcHeadPosition_);
669 
670     // calculate focused item index on circle
671     int32_t index = GetPositionItemIndex(focusedItem_);
672     double focusAngle = itemInfo_[index][TABLE_ANGLE];
673     double expectFocusAngle = arcTailPosition_ - perItemExtent_;
674     double offsetAngle = expectFocusAngle - focusAngle;
675 
676     if (focusAngle > expectFocusAngle) {
677         // items need move
678         RotateItems(offsetAngle); // update items position
679         SetItemsVisible(focusAngle - (arcTailPosition_ - arcHeadPosition_ - perItemExtent_), focusAngle);
680     } else {
681         // items do not need move, reset position directly
682         ResetItemsPosition();
683         SetItemsVisible(arcHeadPosition_, arcTailPosition_ - perItemExtent_);
684     }
685     SetCollapseItemPosition(value);
686     MarkNeedRender();
687 }
688 
SetCollapseItemPosition(double position)689 void RenderIndexerCircle::SetCollapseItemPosition(double position)
690 {
691     if (GetChildren().empty()) {
692         return;
693     }
694     auto item = GetChildren().back();
695     if (!item) {
696         return;
697     }
698 
699     double finalPosition = position;
700     if (itemCount_ <= collapseItemCount_) {
701         item->SetVisible(false);
702         return;
703     } else {
704         item->SetVisible(true);
705     }
706 
707     if (finalPosition < itemInfo_[collapseItemCount_][TABLE_ANGLE]) {
708         finalPosition = itemInfo_[collapseItemCount_][TABLE_ANGLE];
709     }
710 
711     if (position > itemInfo_[itemMaxCount_ - 1][TABLE_ANGLE]) {
712         finalPosition = itemInfo_[itemMaxCount_ - 1][TABLE_ANGLE];
713     }
714 
715     SetItemPosition(item, finalPosition);
716     collapseItemPosition_ = finalPosition;
717 
718     // rotate the collapse item ">"
719     bool collapse = false;
720     if (curStatus_ == IndexerItemStatus::EXPAND) {
721         collapse = true;
722     }
723     if (curStatus_ == IndexerItemStatus::ANIMATION && nextStatus_ == IndexerItemStatus::EXPAND &&
724         NearEqual(collapseItemPosition_, itemInfo_[itemMaxCount_ - 1][TABLE_ANGLE])) {
725         collapse = true;
726     }
727     // rotate angle
728     double rotate = collapseItemPosition_ + M_PI_2;
729     if (collapse) {
730         rotate += M_PI;
731     }
732 
733     RefPtr<RenderIndexerItem> collapseItem = AceType::DynamicCast<RenderIndexerItem>(GetChildren().back());
734     if (collapseItem) {
735         collapseItem->SetRotate(rotate * INDEXER_ANGLE_TO_RADIAN / M_PI);
736     }
737 }
738 
CollapseItemHitTest(const Offset & position)739 bool RenderIndexerCircle::CollapseItemHitTest(const Offset& position)
740 {
741     // do not exist a collapse item
742     if (GetChildren().empty() || !hasCollapseItem_) {
743         return false;
744     }
745     // exist a collapse item but not visible
746     auto collapse = GetChildren().back();
747     if (collapse && !collapse->GetVisible()) {
748         return false;
749     }
750     double hotRgnRadius = hotRgnSize_ * HALF;
751     double touchAngle = GetPositionAngle(position);
752     double touchRadiusTail = hotRgnRadius / (outerRadius_ - itemSize_ * HALF);
753     if (touchAngle < collapseItemPosition_ - perItemExtent_ * HALF ||
754         touchAngle > collapseItemPosition_ + touchRadiusTail) {
755         LOGI("[indexer] CollapseItemHitTest, NOT HIT");
756         return false;
757     }
758 
759     double centerX = outerRadius_ + (outerRadius_ - itemSize_ * HALF) * sin(M_PI_2 + collapseItemPosition_);
760     double centerY = outerRadius_ - (outerRadius_ - itemSize_ * HALF) * cos(M_PI_2 + collapseItemPosition_);
761     double distance = pow(position.GetX() - centerX, 2) + pow(position.GetY() - centerY, 2);
762     if (distance > hotRgnRadius * hotRgnRadius) {
763         LOGI("[indexer] CollapseItemHitTest, NOT HIT");
764         return false;
765     } else {
766         LOGI("[indexer] CollapseItemHitTest, HIT");
767         return true;
768     }
769 }
770 
TouchOnCollapseArc(const Offset & position)771 void RenderIndexerCircle::TouchOnCollapseArc(const Offset& position)
772 {
773     if (curStatus_ != IndexerItemStatus::COLLAPSE) {
774         return;
775     }
776 
777     double touchAngle = GetPositionAngle(position);
778     double touchRadius = hotRgnSize_ / (outerRadius_ - itemSize_ * HALF) * HALF;
779     if (touchAngle < arcHeadPosition_ - touchRadius || touchAngle > arcTailPosition_ - perItemExtent_ + touchRadius) {
780         return;
781     }
782 
783     // focus
784     int32_t curItem = GetNearestItem(touchAngle);
785     curItem = GetActualItemIndex(curItem);
786     // to except collapse item
787     if (curItem == INVALID_INDEX || curItem >= itemCount_ - 1) {
788         return;
789     }
790     if (curItem != curHitItem_) {
791         curHitItem_ = curItem;
792         SetItemsFocused(curItem);
793     }
794 
795     MarkNeedRender();
796 }
797 
RotateItems(double arcOffset)798 void RenderIndexerCircle::RotateItems(double arcOffset)
799 {
800     int32_t count = 0;
801     for (auto item : items_) {
802         // to except collapse item
803         if (count == itemCount_ - 1 || count >= itemMaxCount_) {
804             break;
805         }
806 
807         if (!NeedProcess(item)) {
808             continue;
809         }
810 
811         // calculate item position
812         SetItemPosition(item, itemInfo_[count][TABLE_ANGLE] + arcOffset);
813         ++count;
814     }
815     arcHeadOffset_ = arcOffset;
816 }
817 
SetItemsVisible(double arcHead,double arcTail)818 void RenderIndexerCircle::SetItemsVisible(double arcHead, double arcTail)
819 {
820     if (curStatus_ == IndexerItemStatus::EXPAND) {
821         for (auto item : items_) {
822             item->SetVisible(true);
823         }
824         return;
825     }
826 
827     int32_t count = 0;
828     double visibleHead = arcHead - perItemExtent_ * HALF * HALF;
829     double visibleTail = arcTail + perItemExtent_ * HALF * HALF;
830     for (auto item : items_) {
831         if (count >= itemCount_ - 1 || count >= itemMaxCount_) {
832             break;
833         }
834         if (!NeedProcess(item)) {
835             continue;
836         }
837         item->SetVisible(itemInfo_[count][TABLE_ANGLE] > visibleHead && itemInfo_[count][TABLE_ANGLE] < visibleTail);
838         ++count;
839     }
840 }
841 
GetPositionItemIndex(int32_t index)842 int32_t RenderIndexerCircle::GetPositionItemIndex(int32_t index)
843 {
844     int32_t positionIndex = 0;
845     int32_t count = 0;
846     for (auto item : items_) {
847         if (count == index) {
848             break;
849         }
850         if (NeedProcess(item)) {
851             positionIndex++;
852         }
853         count++;
854     }
855 
856     return positionIndex;
857 }
858 
GetActualItemIndex(int32_t positionIndex)859 int32_t RenderIndexerCircle::GetActualItemIndex(int32_t positionIndex)
860 {
861     int32_t index = 0;
862     int32_t count = 0;
863     for (auto item : items_) {
864         if (NeedProcess(item)) {
865             count++;
866         }
867         if (positionIndex == count - 1) {
868             break;
869         }
870         index++;
871     }
872     return index;
873 }
874 
HandleRotation(double value)875 void RenderIndexerCircle::HandleRotation(double value)
876 {
877     rotationStepValue_ += value;
878     if (GreatOrEqual(rotationStepValue_, ROTATION_THRESHOLD)) {
879         // ShowPrevious
880         for (int32_t i = focusedItem_ - 1; i >= 0; i--) {
881             auto indexerItem = GetSpecificItem(i);
882             if (indexerItem && indexerItem->GetKeyCount() > 0 && NeedProcess(indexerItem)) {
883                 SetItemsFocused(i);
884                 break;
885             }
886         }
887         rotationStepValue_ = 0.0;
888     } else if (LessOrEqual(rotationStepValue_, -ROTATION_THRESHOLD)) {
889         // ShowNext
890         for (int32_t i = focusedItem_ + 1; i < itemCount_; i++) {
891             auto indexerItem = GetSpecificItem(i);
892             if (indexerItem && indexerItem->GetKeyCount() > 0 && NeedProcess(indexerItem)) {
893                 SetItemsFocused(i);
894                 break;
895             }
896         }
897         rotationStepValue_ = 0.0;
898     }
899 }
900 
RotationTest(const RotationEvent & event)901 bool RenderIndexerCircle::RotationTest(const RotationEvent& event)
902 {
903     if (curStatus_ != IndexerItemStatus::EXPAND) {
904         return false;
905     }
906     HandleRotation(event.value);
907     return true;
908 }
909 
GetNearestItem(const Offset & position,bool move)910 int32_t RenderIndexerCircle::GetNearestItem(const Offset& position, bool move)
911 {
912     double touchAngle = GetPositionAngle(position);
913     int32_t positionIndex = round((touchAngle - arcHeadPosition_ - arcHeadOffset_) / perItemExtent_);
914 
915     // calculate actual index of indexer by position
916     int32_t ret = GetActualItemIndex(positionIndex);
917     if (ret < 0 || ret >= itemCount_) {
918         LOGW("[indexer] GetNearestItem failed, ret:%{public}d", ret);
919         return INVALID_INDEX;
920     }
921 
922     double hotRgnRadius = hotRgnSize_ * HALF;
923     double itemCenterRadius = outerRadius_ - itemSize_ * HALF;
924     if (!move) {
925         double centerX = outerRadius_ + itemCenterRadius * itemInfo_[positionIndex][TABLE_POSITION_X];
926         double centerY = outerRadius_ + itemCenterRadius * itemInfo_[positionIndex][TABLE_POSITION_Y];
927         double distance = (position.GetX() - centerX) * (position.GetX() - centerX) +
928                           (position.GetY() - centerY) * (position.GetY() - centerY);
929         if (distance > hotRgnRadius * hotRgnRadius) {
930             return INVALID_INDEX;
931         }
932     }
933     return ret;
934 }
935 
GetNearestItem(double position)936 int32_t RenderIndexerCircle::GetNearestItem(double position)
937 {
938     return round((position - arcHeadPosition_ - arcHeadOffset_) / perItemExtent_);
939 }
940 
NeedRotation() const941 bool RenderIndexerCircle::NeedRotation() const
942 {
943     return curStatus_ == IndexerItemStatus::EXPAND;
944 }
945 
NeedProcess(const RefPtr<RenderNode> & item) const946 bool RenderIndexerCircle::NeedProcess(const RefPtr<RenderNode>& item) const
947 {
948     auto indexerItem = AceType::DynamicCast<RenderIndexerItem>(item);
949     if (indexerItem && (indexerItem->GetItemType() == curItemType_ || indexerItem->IsItemPrimary())) {
950         return true;
951     }
952     return false;
953 }
954 
ChangeLanguage(int32_t index)955 bool RenderIndexerCircle::ChangeLanguage(int32_t index)
956 {
957     if (!NeedChangeIndexer(index)) {
958         return false;
959     }
960     curItemType_ = 1 - curItemType_;
961     FireIndexerChangeEvent();
962     SetItemsFocused(index, true, false);
963     MarkNeedLayout(true);
964     return true;
965 }
966 
GetPositionAngle(const Offset & position)967 double RenderIndexerCircle::GetPositionAngle(const Offset& position)
968 {
969     double touchAngle = atan2(position.GetY() - outerRadius_, position.GetX() - outerRadius_);
970     double itemRadius = itemSize_ / (outerRadius_ - itemSize_ * HALF) * HALF;
971     if (touchAngle < arcHeadPosition_ - itemRadius) {
972         touchAngle += M_PI * DOUBLE;
973     }
974     return touchAngle;
975 }
976 
SetItemsFocused(int32_t index,bool force,bool smooth)977 void RenderIndexerCircle::SetItemsFocused(int32_t index, bool force, bool smooth)
978 {
979     if (index < 0 || index >= itemCount_ - 1) {
980         LOGE("Invalid index:%{public}d", index);
981         return;
982     }
983 
984     RefPtr<RenderIndexerItem> curItem = GetSpecificItem(index);
985     if (!curItem || curItem->GetKeyCount() <= 0) {
986         LOGE("Invalid indexer item:%{public}d", index);
987         return;
988     }
989     if (focusedItem_ == index) {
990         if (force) {
991             curItem->SetClicked(true);
992             MoveList(curItem->GetSectionIndex());
993         }
994         return;
995     }
996 
997     // change to correct index and move list to correct position
998     if (smooth) {
999         BeginFocusAnimation(focusedItem_, index);
1000     }
1001     if (IsValidIndicatorBox()) {
1002         indicatorBox_->SetColor(Color::TRANSPARENT, false);
1003         itemColorEnabled_ = true;
1004     }
1005     curItem->SetClicked(true);
1006     MoveList(curItem->GetSectionIndex());
1007 
1008     // Make pre clicked item blur.
1009     auto preItem = GetSpecificItem(focusedItem_);
1010     if (preItem) {
1011         preItem->SetClicked(false);
1012     }
1013     focusedItem_ = index;
1014     isInitFocus_ = false;
1015     UpdateBubbleText();
1016 }
1017 
FireIndexerChangeEvent() const1018 void RenderIndexerCircle::FireIndexerChangeEvent() const
1019 {
1020     if (indexerChangeEvent_) {
1021         LOGI("indexerChangeEvent_, curItemType_ = %{public}d.", curItemType_);
1022         std::string param =
1023             std::string(R"("indexerchange",{"local":)").append(curItemType_ == 0 ? "true" : "false").append("},null");
1024         indexerChangeEvent_(param);
1025     }
1026 }
1027 
1028 } // namespace OHOS::Ace
1029