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