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