1 /*
2  * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "core/components/picker/render_picker_column.h"
17 
18 #include "base/log/event_report.h"
19 
20 namespace OHOS::Ace {
21 namespace {
22 
23 constexpr double PICKER_ROTATION_SENSITIVITY_NORMAL = 0.6;
24 const std::string& VIBRATOR_TYPE_WATCH_CROWN_STRENGTH2 = "watchhaptic.crown.strength2";
25 
26 }
27 
Update(const RefPtr<Component> & component)28 void RenderPickerColumn::Update(const RefPtr<Component>& component)
29 {
30     auto column = AceType::DynamicCast<PickerColumnComponent>(component);
31     if (!column) {
32         LOGE("input component is incorrect type or null.");
33         EventReport::SendRenderException(RenderExcepType::RENDER_COMPONENT_ERR);
34         return;
35     }
36 
37     auto theme = column->GetTheme();
38     if (!theme) {
39         LOGE("theme of component is null.");
40         EventReport::SendComponentException(ComponentExcepType::GET_THEME_ERR);
41         return;
42     }
43 
44     auto toss = column->GetToss();
45     if (!toss) {
46         return;
47     }
48 
49     data_ = column;
50     toss->SetColumn(AceType::WeakClaim(this));
51     toss->SetPipelineContext(GetContext());
52     jumpInterval_ = theme->GetJumpInterval();
53     columnMargin_ = theme->GetColumnIntervalMargin();
54     rotateInterval_ = theme->GetRotateInterval();
55 
56     needVibrate_ = column->GetNeedVibrate();
57     auto context = context_.Upgrade();
58     if (needVibrate_ && !vibrator_ && context) {
59         vibrator_ = VibratorProxy::GetInstance().GetVibrator(context->GetTaskExecutor());
60     }
61 
62     MarkNeedLayout();
63 }
64 
HandleFinished(bool success)65 bool RenderPickerColumn::HandleFinished(bool success)
66 {
67     if (!data_) {
68         LOGE("data component is null.");
69         return false;
70     }
71 
72     if (!data_->GetInDialog()) {
73         return false;
74     }
75 
76     data_->HandleFinishCallback(success);
77     return true;
78 }
79 
HandleFocus(bool focus)80 void RenderPickerColumn::HandleFocus(bool focus)
81 {
82     focused_ = focus;
83     rotateAngle_ = 0.0;
84     for (const auto& option : options_) {
85         if (!option->GetSelected()) {
86             continue;
87         }
88         option->UpdateFocus(focus);
89         break;
90     }
91 }
92 
GetColumnTag() const93 std::string RenderPickerColumn::GetColumnTag() const
94 {
95     if (!data_) {
96         LOGE("data component is null.");
97         return "";
98     }
99 
100     return data_->GetColumnTag();
101 }
102 
GetWidthRatio() const103 uint32_t RenderPickerColumn::GetWidthRatio() const
104 {
105     if (!data_) {
106         LOGE("data component is null.");
107         return 1; // default width ratio is 1:1
108     }
109 
110     return data_->GetWidthRatio();
111 }
112 
OnTouchTestHit(const Offset & coordinateOffset,const TouchRestrict & touchRestrict,TouchTestResult & result)113 void RenderPickerColumn::OnTouchTestHit(
114     const Offset& coordinateOffset, const TouchRestrict& touchRestrict, TouchTestResult& result)
115 {
116     auto context = context_.Upgrade();
117     if (!panRecognizer_ && context) {
118         PanDirection panDirection;
119         panDirection.type = PanDirection::VERTICAL;
120         panRecognizer_ = AceType::MakeRefPtr<PanRecognizer>(
121             context, DEFAULT_PAN_FINGER, panDirection, DEFAULT_PAN_DISTANCE.ConvertToPx());
122         panRecognizer_->SetOnActionStart([weak = AceType::WeakClaim(this)](const GestureEvent& event) {
123             auto refPtr = weak.Upgrade();
124             if (refPtr) {
125                 refPtr->HandleDragStart(event);
126             }
127         });
128         panRecognizer_->SetOnActionUpdate([weak = AceType::WeakClaim(this)](const GestureEvent& event) {
129             auto refPtr = weak.Upgrade();
130             if (refPtr) {
131                 refPtr->HandleDragMove(event);
132             }
133         });
134         panRecognizer_->SetOnActionEnd([weak = AceType::WeakClaim(this)](const GestureEvent& event) {
135             auto refPtr = weak.Upgrade();
136             if (refPtr) {
137                 refPtr->HandleDragEnd();
138             }
139         });
140         panRecognizer_->SetOnActionCancel([weak = AceType::WeakClaim(this)]() {
141             auto refPtr = weak.Upgrade();
142             if (refPtr) {
143                 refPtr->HandleDragEnd();
144             }
145         });
146     }
147 
148     panRecognizer_->SetCoordinateOffset(coordinateOffset);
149     result.emplace_back(panRecognizer_);
150 }
151 
InnerHandleScroll(bool isDown)152 bool RenderPickerColumn::InnerHandleScroll(bool isDown)
153 {
154     if (!data_ || !data_->GetOptionCount()) {
155         LOGE("options is empty.");
156         return false;
157     }
158 
159     if (needVibrate_ && vibrator_) {
160         vibrator_->Vibrate(VIBRATOR_TYPE_WATCH_CROWN_STRENGTH2);
161     }
162 
163     uint32_t totalOptionCount = data_->GetOptionCount();
164     uint32_t currentIndex = data_->GetCurrentIndex();
165     if (isDown) {
166         currentIndex = (totalOptionCount + currentIndex + 1) % totalOptionCount; // index add one
167     } else {
168         currentIndex = (totalOptionCount + currentIndex - 1) % totalOptionCount; // index reduce one
169     }
170     data_->SetCurrentIndex(currentIndex);
171     FlushCurrentOptions();
172     data_->HandleChangeCallback(isDown, true);
173     return true;
174 }
175 
RotationTest(const RotationEvent & event)176 bool RenderPickerColumn::RotationTest(const RotationEvent& event)
177 {
178     if (!focused_) {
179         return false;
180     }
181 
182     rotateAngle_ += event.value * PICKER_ROTATION_SENSITIVITY_NORMAL;
183     if (rotateAngle_ > rotateInterval_) {
184         HandleScroll(false);
185         rotateAngle_ = 0.0;
186         return true;
187     }
188 
189     if (rotateAngle_ < 0 - rotateInterval_) {
190         HandleScroll(true);
191         rotateAngle_ = 0.0;
192         return true;
193     }
194 
195     return true;
196 }
197 
PerformLayout()198 void RenderPickerColumn::PerformLayout()
199 {
200     double value = NormalizeToPx(jumpInterval_);
201     jumpInterval_ = Dimension(value, DimensionUnit::PX);
202     value = NormalizeToPx(columnMargin_);
203     columnMargin_ = Dimension(value, DimensionUnit::PX);
204     if (!clip_ || !column_) {
205         LOGE("can not get render of child column or clip.");
206         return;
207     }
208 
209     double x = columnMargin_.Value() / 2.0; // left and right margin is half of it
210     double y = 0.0;
211     if (GetLayoutParam().GetMinSize() == GetLayoutParam().GetMaxSize()) {
212         auto size = GetLayoutParam().GetMaxSize();
213         SetLayoutSize(size);
214         column_->Layout(LayoutParam());
215         y = (column_->GetLayoutSize().Height() - size.Height()) / 2.0; // center position
216         if (LessNotEqual(y, 0)) {
217             y = 0.0;
218         }
219         clip_->SetHeight(size.Height());
220         size.SetWidth(size.Width() - columnMargin_.Value());
221         size.SetHeight(size.Height() + y);
222         clip_->SetOffsetY(y);
223         clip_->SetPosition(Offset(x, 0.0 - y));
224         LayoutParam layout;
225         layout.SetFixedSize(size);
226         clip_->Layout(layout);
227     } else {
228         double fixHeight = 0.0;
229         clip_->SetPosition(Offset(x, y));
230         if (!NearZero(fixHeight)) {
231             auto layout = GetLayoutParam();
232             auto max = layout.GetMaxSize();
233             auto min = layout.GetMinSize();
234             max.SetHeight(fixHeight);
235             min.SetHeight(fixHeight);
236             layout.SetMaxSize(max);
237             layout.SetMinSize(min);
238             clip_->Layout(layout);
239         } else if (NearZero(adjustHeight_)) {
240             clip_->Layout(LayoutParam());
241         } else {
242             auto layout = GetLayoutParam();
243             auto maxSize = layout.GetMaxSize();
244             maxSize.SetHeight(maxSize.Height() - adjustHeight_);
245             layout.SetMaxSize(maxSize);
246             clip_->Layout(layout);
247         }
248         auto size = clip_->GetLayoutSize();
249         size.SetWidth(size.Width() + columnMargin_.Value());
250         SetLayoutSize(size);
251     }
252 
253     LayoutSplitter();
254     CreateAnimation();
255 }
256 
LayoutSplitter()257 void RenderPickerColumn::LayoutSplitter()
258 {
259     if (!splitter_) {
260         return;
261     }
262 
263     auto size = GetLayoutSize();
264     splitter_->Layout(LayoutParam());
265     double x = size.Width() - splitter_->GetLayoutSize().Width() / 2.0;     // place center of right.
266     double y = (size.Height() - splitter_->GetLayoutSize().Height()) / 2.0; // place center
267     splitter_->SetPosition(Offset(x, y));
268 }
269 
UpdateAccessibility()270 void RenderPickerColumn::UpdateAccessibility()
271 {
272     auto pipeline = context_.Upgrade();
273     if (!pipeline) {
274         LOGE("pipeline is null.");
275         return;
276     }
277     auto manager = pipeline->GetAccessibilityManager();
278     if (!manager || data_->GetNodeId() < 0) {
279         LOGE("accessibility manager is null or column node id is invalidate.");
280         return;
281     }
282     auto node = manager->GetAccessibilityNodeById(data_->GetNodeId());
283     if (!node) {
284         LOGE("can not get accessibility node by id");
285         return;
286     }
287 
288     node->SetText(data_->GetPrefix() + data_->GetCurrentText() + data_->GetSuffix());
289     node->SetFocusableState(true);
290     node->SetFocusedState(focused_);
291     node->SetScrollableState(true);
292     auto viewScale = pipeline->GetViewScale();
293     auto leftTop = GetGlobalOffset();
294     node->SetLeft(leftTop.GetX() * viewScale);
295     node->SetTop(leftTop.GetY() * viewScale);
296     auto size = GetLayoutSize();
297     node->SetWidth(size.Width() * viewScale);
298     node->SetHeight(size.Height() * viewScale);
299     if (!nodeHandlerSetted_) {
300         nodeHandlerSetted_ = true;
301         node->AddSupportAction(AceAction::ACTION_SCROLL_BACKWARD);
302         node->AddSupportAction(AceAction::ACTION_SCROLL_FORWARD);
303         node->AddSupportAction(AceAction::ACTION_FOCUS);
304         node->SetActionScrollForward([weak = WeakClaim(this)]() {
305             auto pickerColumn = weak.Upgrade();
306             if (pickerColumn) {
307                 pickerColumn->HandleScroll(true);
308             }
309             return true;
310         });
311         node->SetActionScrollBackward([weak = WeakClaim(this)]() {
312             auto pickerColumn = weak.Upgrade();
313             if (pickerColumn) {
314                 pickerColumn->HandleScroll(false);
315             }
316             return true;
317         });
318         node->SetActionFocusImpl([weak = WeakClaim(this)]() {
319             auto pickerColumn = weak.Upgrade();
320             if (pickerColumn) {
321                 pickerColumn->HandleFocus(true);
322             }
323         });
324     }
325 }
326 
HandleDragStart(const GestureEvent & event)327 void RenderPickerColumn::HandleDragStart(const GestureEvent& event)
328 {
329     bool isPhone = SystemProperties::GetDeviceType() == DeviceType::PHONE;
330     if (data_ && !isPhone) {
331         data_->HandleRequestFocus();
332     }
333 
334     if (!data_ || !data_->GetToss()) {
335         return;
336     }
337 
338     auto toss = data_->GetToss();
339     yOffset_ = event.GetGlobalPoint().GetY();
340     toss->SetStart(yOffset_);
341     yLast_ = yOffset_;
342     pressed_ = true;
343 }
344 
HandleDragMove(const GestureEvent & event)345 void RenderPickerColumn::HandleDragMove(const GestureEvent& event)
346 {
347     if (event.GetInputEventType() == InputEventType::AXIS) {
348         InnerHandleScroll(LessNotEqual(event.GetDelta().GetY(), 0.0));
349         return;
350     }
351 
352     if (!pressed_) {
353         LOGE("not pressed.");
354         return;
355     }
356 
357     if (!data_ || !data_->GetToss()) {
358         return;
359     }
360 
361     auto toss = data_->GetToss();
362     double y = event.GetGlobalPoint().GetY();
363     if (NearEqual(y, yLast_, 1.0)) { // if changing less than 1.0, no need to handle
364         return;
365     }
366     toss->SetEnd(y);
367     UpdatePositionY(y);
368 }
369 
UpdatePositionY(double y)370 void RenderPickerColumn::UpdatePositionY(double y)
371 {
372     yLast_ = y;
373     double dragDelta = yLast_ - yOffset_;
374     if (!CanMove(LessNotEqual(dragDelta, 0))) {
375         return;
376     }
377     // the abs of drag delta is less than jump interval.
378     if (LessNotEqual(0.0 - jumpInterval_.Value(), dragDelta) && LessNotEqual(dragDelta, jumpInterval_.Value())) {
379         ScrollOption(dragDelta);
380         return;
381     }
382 
383     InnerHandleScroll(LessNotEqual(dragDelta, 0.0));
384     double jumpDelta = (LessNotEqual(dragDelta, 0.0) ? jumpInterval_.Value() : 0.0 - jumpInterval_.Value());
385     ScrollOption(jumpDelta);
386     yOffset_ = y - jumpDelta;
387 }
388 
UpdateToss(double y)389 void RenderPickerColumn::UpdateToss(double y)
390 {
391     UpdatePositionY(y);
392 }
393 
TossStoped()394 void RenderPickerColumn::TossStoped()
395 {
396     yOffset_ = 0.0;
397     yLast_ = 0.0;
398     ScrollOption(0.0);
399 }
400 
HandleDragEnd()401 void RenderPickerColumn::HandleDragEnd()
402 {
403     pressed_ = false;
404 
405     if (!data_ || !data_->GetToss()) {
406         return;
407     }
408 
409     auto toss = data_->GetToss();
410     if (!NotLoopOptions() && toss->Play()) {
411         return;
412     }
413 
414     yOffset_ = 0.0;
415     yLast_ = 0.0;
416 
417     if (!animationCreated_) {
418         ScrollOption(0.0);
419     } else {
420         auto curve = CreateAnimation(scrollDelta_, 0.0);
421         fromController_->ClearInterpolators();
422         fromController_->AddInterpolator(curve);
423         fromController_->Play();
424     }
425 }
426 
UpdateRenders()427 void RenderPickerColumn::UpdateRenders()
428 {
429     ClearRenders();
430     GetRenders();
431 }
432 
GetRenders(const RefPtr<RenderNode> & render)433 void RenderPickerColumn::GetRenders(const RefPtr<RenderNode>& render)
434 {
435     if (!render) {
436         return;
437     }
438 
439     if (AceType::InstanceOf<RenderText>(render)) {
440         auto parent = render->GetParent().Upgrade();
441         if (AceType::InstanceOf<RenderPickerColumn>(parent)) {
442             splitter_ = AceType::DynamicCast<RenderText>(render);
443             return;
444         }
445     }
446 
447     if (AceType::InstanceOf<RenderPickerOption>(render)) {
448         options_.emplace_back(AceType::DynamicCast<RenderPickerOption>(render));
449         return;
450     }
451 
452     if (AceType::InstanceOf<RenderFlex>(render)) {
453         column_ = AceType::DynamicCast<RenderFlex>(render);
454     }
455 
456     if (AceType::InstanceOf<RenderClip>(render)) {
457         clip_ = AceType::DynamicCast<RenderClip>(render);
458     }
459 
460     if (AceType::InstanceOf<RenderDisplay>(render)) {
461         display_ = AceType::DynamicCast<RenderDisplay>(render);
462     }
463 
464     for (const auto& child : render->GetChildren()) {
465         GetRenders(child);
466     }
467 }
468 
GetRenders()469 void RenderPickerColumn::GetRenders()
470 {
471     options_.clear();
472     GetRenders(AceType::Claim(this));
473 }
474 
ClearRenders()475 void RenderPickerColumn::ClearRenders()
476 {
477     display_ = nullptr;
478     column_ = nullptr;
479     clip_ = nullptr;
480     splitter_ = nullptr;
481     options_.clear();
482 }
483 
FlushCurrentOptions()484 void RenderPickerColumn::FlushCurrentOptions()
485 {
486     if (!data_) {
487         LOGE("data is null.");
488         return;
489     }
490 
491     uint32_t totalOptionCount = data_->GetOptionCount();
492     uint32_t currentIndex = data_->GetCurrentIndex();
493     currentIndex = currentIndex % totalOptionCount;
494     uint32_t showOptionCount = options_.size();
495     uint32_t selectedIndex = showOptionCount / 2; // the center option is selected.
496     for (uint32_t index = 0; index < showOptionCount; index++) {
497         auto sum = totalOptionCount + currentIndex + index;
498         uint32_t optionIndex = (sum > selectedIndex ? sum - selectedIndex : 0) % totalOptionCount;
499         int diffIndex = static_cast<int>(index) - static_cast<int>(selectedIndex);
500         int virtualIndex = static_cast<int>(currentIndex) + diffIndex;
501         bool virtualIndexValidate = virtualIndex >= 0 && virtualIndex < static_cast<int>(totalOptionCount);
502         if (NotLoopOptions() && !virtualIndexValidate) {
503             options_[index]->UpdateValue(optionIndex, "");
504             continue;
505         }
506         std::string optionText =
507             (index != selectedIndex ? data_->GetOption(optionIndex)
508                                     : data_->GetPrefix() + data_->GetOption(optionIndex) + data_->GetSuffix());
509         options_[index]->UpdateValue(optionIndex, optionText);
510     }
511 }
512 
NotLoopOptions() const513 bool RenderPickerColumn::NotLoopOptions() const
514 {
515     if (!data_) {
516         LOGE("data is null.");
517         return false;
518     }
519 
520     uint32_t totalOptionCount = data_->GetOptionCount();
521     uint32_t showOptionCount = options_.size();
522     return totalOptionCount <= showOptionCount / 2 + 1; // the critical value of loop condition.
523 }
524 
CanMove(bool isDown) const525 bool RenderPickerColumn::CanMove(bool isDown) const
526 {
527     if (!NotLoopOptions()) {
528         return true;
529     }
530 
531     if (!data_) {
532         LOGE("data is null.");
533         return true;
534     }
535 
536     int currentIndex = static_cast<int>(data_->GetCurrentIndex());
537     int totalOptionCount = static_cast<int>(data_->GetOptionCount());
538     int nextVirtualIndex = isDown ? currentIndex + 1 : currentIndex - 1;
539     return nextVirtualIndex >= 0 && nextVirtualIndex < totalOptionCount;
540 }
541 
ScrollOption(double delta)542 void RenderPickerColumn::ScrollOption(double delta)
543 {
544     for (const auto& option : options_) {
545         option->UpdateScrollDelta(delta);
546     }
547     scrollDelta_ = delta;
548 }
549 
CreateAnimation()550 void RenderPickerColumn::CreateAnimation()
551 {
552     if (animationCreated_) {
553         return;
554     }
555 
556     toBottomCurve_ = CreateAnimation(0.0, jumpInterval_.Value());
557     toTopCurve_ = CreateAnimation(0.0, 0.0 - jumpInterval_.Value());
558     toController_ = CREATE_ANIMATOR(context_);
559     toController_->SetDuration(200); // 200ms for animation that from zero to outer.
560     auto weak = AceType::WeakClaim(this);
561     toController_->AddStopListener([weak]() {
562         auto column = weak.Upgrade();
563         if (column) {
564             column->HandleCurveStopped();
565         } else {
566             LOGE("render picker column is null.");
567         }
568     });
569     fromBottomCurve_ = CreateAnimation(jumpInterval_.Value(), 0.0);
570     fromTopCurve_ = CreateAnimation(0.0 - jumpInterval_.Value(), 0.0);
571     fromController_ = CREATE_ANIMATOR(context_);
572     fromController_->SetDuration(150); // 150ms for animation that from outer to zero.
573     animationCreated_ = true;
574 }
575 
CreateAnimation(double from,double to)576 RefPtr<CurveAnimation<double>> RenderPickerColumn::CreateAnimation(double from, double to)
577 {
578     auto weak = AceType::WeakClaim(this);
579     auto curve = AceType::MakeRefPtr<CurveAnimation<double>>(from, to, Curves::FRICTION);
580     curve->AddListener(Animation<double>::ValueCallback([weak](double value) {
581         auto column = weak.Upgrade();
582         if (column) {
583             column->ScrollOption(value);
584         } else {
585             LOGE("render picker column is null.");
586         }
587     }));
588     return curve;
589 }
590 
HandleCurveStopped()591 void RenderPickerColumn::HandleCurveStopped()
592 {
593     if (!animationCreated_) {
594         LOGE("animation not created.");
595         return;
596     }
597 
598     if (NearZero(scrollDelta_)) {
599         return;
600     }
601 
602     ScrollOption(0.0 - scrollDelta_);
603     InnerHandleScroll(GreatNotEqual(scrollDelta_, 0.0));
604 
605     fromController_->ClearInterpolators();
606     if (LessNotEqual(scrollDelta_, 0.0)) {
607         fromController_->AddInterpolator(fromTopCurve_);
608     } else {
609         fromController_->AddInterpolator(fromBottomCurve_);
610     }
611     fromController_->Play();
612 }
613 
HandleScroll(bool isDown)614 bool RenderPickerColumn::HandleScroll(bool isDown)
615 {
616     if (!CanMove(isDown)) {
617         return false;
618     }
619 
620     if (!animationCreated_) {
621         return InnerHandleScroll(isDown);
622     }
623 
624     toController_->ClearInterpolators();
625     if (isDown) {
626         toController_->AddInterpolator(toTopCurve_);
627     } else {
628         toController_->AddInterpolator(toBottomCurve_);
629     }
630     toController_->Play();
631     return true;
632 }
633 
634 } // namespace OHOS::Ace
635