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