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