• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #include "core/gestures/pan_recognizer.h"
20 
21 namespace OHOS::Ace {
22 namespace {
23 
24 constexpr double PICKER_ROTATION_SENSITIVITY_NORMAL = 0.6;
25 const std::string& VIBRATOR_TYPE_WATCH_CROWN_STRENGTH2 = "watchhaptic.crown.strength2";
26 
27 }
28 
Update(const RefPtr<Component> & component)29 void RenderPickerColumn::Update(const RefPtr<Component>& component)
30 {
31     auto column = AceType::DynamicCast<PickerColumnComponent>(component);
32     if (!column) {
33         LOGE("input component is incorrect type or null.");
34         EventReport::SendRenderException(RenderExcepType::RENDER_COMPONENT_ERR);
35         return;
36     }
37 
38     auto theme = column->GetTheme();
39     if (!theme) {
40         LOGE("theme of component is null.");
41         EventReport::SendComponentException(ComponentExcepType::GET_THEME_ERR);
42         return;
43     }
44 
45     auto toss = column->GetToss();
46     if (!toss) {
47         return;
48     }
49 
50     data_ = column;
51     toss->SetColumn(AceType::WeakClaim(this));
52     toss->SetPipelineContext(GetContext());
53     jumpInterval_ = theme->GetJumpInterval();
54     columnMargin_ = theme->GetColumnIntervalMargin();
55     rotateInterval_ = theme->GetRotateInterval();
56 
57     needVibrate_ = column->GetNeedVibrate();
58     auto context = context_.Upgrade();
59     if (needVibrate_ && !vibrator_ && context) {
60         vibrator_ = VibratorProxy::GetInstance().GetVibrator(context->GetTaskExecutor());
61     }
62 
63     MarkNeedLayout();
64 }
65 
HandleFinished(bool success)66 bool RenderPickerColumn::HandleFinished(bool success)
67 {
68     if (!data_) {
69         LOGE("data component is null.");
70         return false;
71     }
72 
73     if (!data_->GetInDialog()) {
74         return false;
75     }
76 
77     data_->HandleFinishCallback(success);
78     return true;
79 }
80 
HandleFocus(bool focus)81 void RenderPickerColumn::HandleFocus(bool focus)
82 {
83     focused_ = focus;
84     rotateAngle_ = 0.0;
85     for (const auto& option : options_) {
86         if (!option->GetSelected()) {
87             continue;
88         }
89         option->UpdateFocus(focus);
90         break;
91     }
92 }
93 
GetColumnTag() const94 std::string RenderPickerColumn::GetColumnTag() const
95 {
96     if (!data_) {
97         LOGE("data component is null.");
98         return "";
99     }
100 
101     return data_->GetColumnTag();
102 }
103 
GetWidthRatio() const104 uint32_t RenderPickerColumn::GetWidthRatio() const
105 {
106     if (!data_) {
107         LOGE("data component is null.");
108         return 1; // default width ratio is 1:1
109     }
110 
111     return data_->GetWidthRatio();
112 }
113 
OnTouchTestHit(const Offset & coordinateOffset,const TouchRestrict & touchRestrict,TouchTestResult & result)114 void RenderPickerColumn::OnTouchTestHit(
115     const Offset& coordinateOffset, const TouchRestrict& touchRestrict, TouchTestResult& result)
116 {
117     auto context = context_.Upgrade();
118     if (!panRecognizer_ && context) {
119         PanDirection panDirection;
120         panDirection.type = PanDirection::VERTICAL;
121         panRecognizer_ = AceType::MakeRefPtr<PanRecognizer>(
122             context, DEFAULT_PAN_FINGER, panDirection, DEFAULT_PAN_DISTANCE.ConvertToPx());
123         panRecognizer_->SetOnActionStart([weak = AceType::WeakClaim(this)](const GestureEvent& event) {
124             auto refPtr = weak.Upgrade();
125             if (refPtr) {
126                 refPtr->HandleDragStart(event);
127             }
128         });
129         panRecognizer_->SetOnActionUpdate([weak = AceType::WeakClaim(this)](const GestureEvent& event) {
130             auto refPtr = weak.Upgrade();
131             if (refPtr) {
132                 refPtr->HandleDragMove(event);
133             }
134         });
135         panRecognizer_->SetOnActionEnd([weak = AceType::WeakClaim(this)](const GestureEvent& event) {
136             auto refPtr = weak.Upgrade();
137             if (refPtr) {
138                 refPtr->HandleDragEnd();
139             }
140         });
141         panRecognizer_->SetOnActionCancel([weak = AceType::WeakClaim(this)]() {
142             auto refPtr = weak.Upgrade();
143             if (refPtr) {
144                 refPtr->HandleDragEnd();
145             }
146         });
147     }
148 
149     panRecognizer_->SetCoordinateOffset(coordinateOffset);
150     result.emplace_back(panRecognizer_);
151 }
152 
InnerHandleScroll(bool isDown)153 bool RenderPickerColumn::InnerHandleScroll(bool isDown)
154 {
155     if (!data_ || !data_->GetOptionCount()) {
156         LOGE("options is empty.");
157         return false;
158     }
159 
160     if (needVibrate_ && vibrator_) {
161         vibrator_->Vibrate(VIBRATOR_TYPE_WATCH_CROWN_STRENGTH2);
162     }
163 
164     uint32_t totalOptionCount = data_->GetOptionCount();
165     uint32_t currentIndex = data_->GetCurrentIndex();
166     if (isDown) {
167         currentIndex = (totalOptionCount + currentIndex + 1) % totalOptionCount; // index add one
168     } else {
169         currentIndex = (totalOptionCount + currentIndex - 1) % totalOptionCount; // index reduce one
170     }
171     data_->SetCurrentIndex(currentIndex);
172     FlushCurrentOptions();
173     data_->HandleChangeCallback(isDown, true);
174     return true;
175 }
176 
RotationTest(const RotationEvent & event)177 bool RenderPickerColumn::RotationTest(const RotationEvent& event)
178 {
179     if (!focused_) {
180         return false;
181     }
182 
183     rotateAngle_ += event.value * PICKER_ROTATION_SENSITIVITY_NORMAL;
184     if (rotateAngle_ > rotateInterval_) {
185         HandleScroll(false);
186         rotateAngle_ = 0.0;
187         return true;
188     }
189 
190     if (rotateAngle_ < 0 - rotateInterval_) {
191         HandleScroll(true);
192         rotateAngle_ = 0.0;
193         return true;
194     }
195 
196     return true;
197 }
198 
PerformLayout()199 void RenderPickerColumn::PerformLayout()
200 {
201     double value = NormalizeToPx(jumpInterval_);
202     jumpInterval_ = Dimension(value, DimensionUnit::PX);
203     value = NormalizeToPx(columnMargin_);
204     columnMargin_ = Dimension(value, DimensionUnit::PX);
205     if (!clip_ || !column_) {
206         LOGE("can not get render of child column or clip.");
207         return;
208     }
209 
210     double x = columnMargin_.Value() / 2.0; // left and right margin is half of it
211     double y = 0.0;
212     if (GetLayoutParam().GetMinSize() == GetLayoutParam().GetMaxSize()) {
213         auto size = GetLayoutParam().GetMaxSize();
214         SetLayoutSize(size);
215         column_->Layout(LayoutParam());
216         y = (column_->GetLayoutSize().Height() - size.Height()) / 2.0; // center position
217         if (LessNotEqual(y, 0)) {
218             y = 0.0;
219         }
220         clip_->SetHeight(size.Height());
221         size.SetWidth(size.Width() - columnMargin_.Value());
222         size.SetHeight(size.Height() + y);
223         clip_->SetOffsetY(y);
224         clip_->SetPosition(Offset(x, 0.0 - y));
225         LayoutParam layout;
226         layout.SetFixedSize(size);
227         clip_->Layout(layout);
228     } else {
229         double fixHeight = 0.0;
230         clip_->SetPosition(Offset(x, y));
231         if (!NearZero(fixHeight)) {
232             auto layout = GetLayoutParam();
233             auto max = layout.GetMaxSize();
234             auto min = layout.GetMinSize();
235             max.SetHeight(fixHeight);
236             min.SetHeight(fixHeight);
237             layout.SetMaxSize(max);
238             layout.SetMinSize(min);
239             clip_->Layout(layout);
240         } else if (NearZero(adjustHeight_)) {
241             clip_->Layout(LayoutParam());
242         } else {
243             auto layout = GetLayoutParam();
244             auto maxSize = layout.GetMaxSize();
245             maxSize.SetHeight(maxSize.Height() - adjustHeight_);
246             layout.SetMaxSize(maxSize);
247             clip_->Layout(layout);
248         }
249         auto size = clip_->GetLayoutSize();
250         size.SetWidth(size.Width() + columnMargin_.Value());
251         SetLayoutSize(size);
252     }
253 
254     LayoutSplitter();
255     CreateAnimation();
256 }
257 
LayoutSplitter()258 void RenderPickerColumn::LayoutSplitter()
259 {
260     if (!splitter_) {
261         return;
262     }
263 
264     auto size = GetLayoutSize();
265     splitter_->Layout(LayoutParam());
266     double x = size.Width() - splitter_->GetLayoutSize().Width() / 2.0;     // place center of right.
267     double y = (size.Height() - splitter_->GetLayoutSize().Height()) / 2.0; // place center
268     splitter_->SetPosition(Offset(x, y));
269 }
270 
UpdateAccessibility()271 void RenderPickerColumn::UpdateAccessibility()
272 {
273     auto pipeline = context_.Upgrade();
274     if (!pipeline) {
275         LOGE("pipeline is null.");
276         return;
277     }
278     auto manager = pipeline->GetAccessibilityManager();
279     if (!manager || data_->GetNodeId() < 0) {
280         LOGE("accessibility manager is null or column node id is invalidate.");
281         return;
282     }
283     auto node = manager->GetAccessibilityNodeById(data_->GetNodeId());
284     if (!node) {
285         LOGE("can not get accessibility node by id");
286         return;
287     }
288 
289     node->SetText(data_->GetPrefix() + data_->GetCurrentText() + data_->GetSuffix());
290     node->SetFocusableState(true);
291     node->SetFocusedState(focused_);
292     node->SetScrollableState(true);
293     auto viewScale = pipeline->GetViewScale();
294     auto leftTop = GetGlobalOffset();
295     node->SetLeft(leftTop.GetX() * viewScale);
296     node->SetTop(leftTop.GetY() * viewScale);
297     auto size = GetLayoutSize();
298     node->SetWidth(size.Width() * viewScale);
299     node->SetHeight(size.Height() * viewScale);
300     if (!nodeHandlerSetted_) {
301         nodeHandlerSetted_ = true;
302         node->AddSupportAction(AceAction::ACTION_SCROLL_BACKWARD);
303         node->AddSupportAction(AceAction::ACTION_SCROLL_FORWARD);
304         node->AddSupportAction(AceAction::ACTION_FOCUS);
305         node->SetActionScrollForward([weak = WeakClaim(this)]() {
306             auto pickerColumn = weak.Upgrade();
307             if (pickerColumn) {
308                 pickerColumn->HandleScroll(true);
309             }
310             return true;
311         });
312         node->SetActionScrollBackward([weak = WeakClaim(this)]() {
313             auto pickerColumn = weak.Upgrade();
314             if (pickerColumn) {
315                 pickerColumn->HandleScroll(false);
316             }
317             return true;
318         });
319         node->SetActionFocusImpl([weak = WeakClaim(this)]() {
320             auto pickerColumn = weak.Upgrade();
321             if (pickerColumn) {
322                 pickerColumn->HandleFocus(true);
323             }
324         });
325     }
326 }
327 
HandleDragStart(const GestureEvent & event)328 void RenderPickerColumn::HandleDragStart(const GestureEvent& event)
329 {
330     bool isPhone = SystemProperties::GetDeviceType() == DeviceType::PHONE;
331     if (data_ && !isPhone) {
332         data_->HandleRequestFocus();
333     }
334 
335     if (!data_ || !data_->GetToss()) {
336         return;
337     }
338 
339     auto toss = data_->GetToss();
340     yOffset_ = event.GetGlobalPoint().GetY();
341     toss->SetStart(yOffset_);
342     yLast_ = yOffset_;
343     pressed_ = true;
344 }
345 
HandleDragMove(const GestureEvent & event)346 void RenderPickerColumn::HandleDragMove(const GestureEvent& event)
347 {
348     if (event.GetInputEventType() == InputEventType::AXIS) {
349         InnerHandleScroll(LessNotEqual(event.GetDelta().GetY(), 0.0));
350         return;
351     }
352 
353     if (!pressed_) {
354         LOGE("not pressed.");
355         return;
356     }
357 
358     if (!data_ || !data_->GetToss()) {
359         return;
360     }
361 
362     auto toss = data_->GetToss();
363     double y = event.GetGlobalPoint().GetY();
364     if (NearEqual(y, yLast_, 1.0)) { // if changing less than 1.0, no need to handle
365         return;
366     }
367     toss->SetEnd(y);
368     UpdatePositionY(y);
369 }
370 
UpdatePositionY(double y)371 void RenderPickerColumn::UpdatePositionY(double y)
372 {
373     yLast_ = y;
374     double dragDelta = yLast_ - yOffset_;
375     if (!CanMove(LessNotEqual(dragDelta, 0))) {
376         return;
377     }
378     // the abs of drag delta is less than jump interval.
379     if (LessNotEqual(0.0 - jumpInterval_.Value(), dragDelta) && LessNotEqual(dragDelta, jumpInterval_.Value())) {
380         ScrollOption(dragDelta);
381         return;
382     }
383 
384     InnerHandleScroll(LessNotEqual(dragDelta, 0.0));
385     double jumpDelta = (LessNotEqual(dragDelta, 0.0) ? jumpInterval_.Value() : 0.0 - jumpInterval_.Value());
386     ScrollOption(jumpDelta);
387     yOffset_ = y - jumpDelta;
388 }
389 
UpdateToss(double y)390 void RenderPickerColumn::UpdateToss(double y)
391 {
392     UpdatePositionY(y);
393 }
394 
TossStoped()395 void RenderPickerColumn::TossStoped()
396 {
397     yOffset_ = 0.0;
398     yLast_ = 0.0;
399     ScrollOption(0.0);
400 }
401 
HandleDragEnd()402 void RenderPickerColumn::HandleDragEnd()
403 {
404     pressed_ = false;
405 
406     if (!data_ || !data_->GetToss()) {
407         return;
408     }
409 
410     auto toss = data_->GetToss();
411     if (!NotLoopOptions() && toss->Play()) {
412         return;
413     }
414 
415     yOffset_ = 0.0;
416     yLast_ = 0.0;
417 
418     if (!animationCreated_) {
419         ScrollOption(0.0);
420     } else {
421         auto curve = CreateAnimation(scrollDelta_, 0.0);
422         fromController_->ClearInterpolators();
423         fromController_->AddInterpolator(curve);
424         fromController_->Play();
425     }
426 }
427 
UpdateRenders()428 void RenderPickerColumn::UpdateRenders()
429 {
430     ClearRenders();
431     GetRenders();
432 }
433 
GetRenders(const RefPtr<RenderNode> & render)434 void RenderPickerColumn::GetRenders(const RefPtr<RenderNode>& render)
435 {
436     if (!render) {
437         return;
438     }
439 
440     if (AceType::InstanceOf<RenderText>(render)) {
441         auto parent = render->GetParent().Upgrade();
442         if (AceType::InstanceOf<RenderPickerColumn>(parent)) {
443             splitter_ = AceType::DynamicCast<RenderText>(render);
444             return;
445         }
446     }
447 
448     if (AceType::InstanceOf<RenderPickerOption>(render)) {
449         options_.emplace_back(AceType::DynamicCast<RenderPickerOption>(render));
450         return;
451     }
452 
453     if (AceType::InstanceOf<RenderFlex>(render)) {
454         column_ = AceType::DynamicCast<RenderFlex>(render);
455     }
456 
457     if (AceType::InstanceOf<RenderClip>(render)) {
458         clip_ = AceType::DynamicCast<RenderClip>(render);
459     }
460 
461     if (AceType::InstanceOf<RenderDisplay>(render)) {
462         display_ = AceType::DynamicCast<RenderDisplay>(render);
463     }
464 
465     for (const auto& child : render->GetChildren()) {
466         GetRenders(child);
467     }
468 }
469 
GetRenders()470 void RenderPickerColumn::GetRenders()
471 {
472     options_.clear();
473     GetRenders(AceType::Claim(this));
474 }
475 
ClearRenders()476 void RenderPickerColumn::ClearRenders()
477 {
478     display_ = nullptr;
479     column_ = nullptr;
480     clip_ = nullptr;
481     splitter_ = nullptr;
482     options_.clear();
483 }
484 
FlushCurrentOptions()485 void RenderPickerColumn::FlushCurrentOptions()
486 {
487     if (!data_) {
488         LOGE("data is null.");
489         return;
490     }
491 
492     uint32_t totalOptionCount = data_->GetOptionCount();
493     uint32_t currentIndex = data_->GetCurrentIndex();
494     currentIndex = currentIndex % totalOptionCount;
495     uint32_t showOptionCount = options_.size();
496     uint32_t selectedIndex = showOptionCount / 2; // the center option is selected.
497     for (uint32_t index = 0; index < showOptionCount; index++) {
498         uint32_t optionIndex = (totalOptionCount + currentIndex + index - selectedIndex) % 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