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/slider/render_slider.h"
17
18 #include "base/log/event_report.h"
19 #include "core/event/ace_event_helper.h"
20
21 namespace OHOS::Ace {
22 namespace {
23
24 constexpr double DOUBLE_TO_PERCENT = 100.0;
25 constexpr double CHANGE_RATIO = 0.2;
26 constexpr double DEFAULT_NORMAL_RADIUS_SCALE = 1.0;
27 constexpr double DEFAULT_LARGE_RADIUS_SCALE = 1.1;
28 constexpr Dimension DEFAULT_OUTSET_TRACK_THICKNESS = 4.0_vp;
29 constexpr Dimension DEFAULT_INSET_TRACK_THICKNESS = 20.0_vp;
30 constexpr int32_t DEFAULT_SLIDER_ANIMATION_DURATION = 150;
31 constexpr int32_t SLIDER_MOVE_DURATION = 100;
32 constexpr Dimension DEFAULT_SLIDER_WIDTH_DP = 260.0_vp;
33 constexpr Dimension DEFAULT_SLIDER_HEIGHT_DP = 40.0_vp;
34 constexpr double INTERMITTENT_RATIO = 0.25;
35 } // namespace
36
RenderSlider()37 RenderSlider::RenderSlider() : RenderNode(true) {}
38
Update(const RefPtr<Component> & component)39 void RenderSlider::Update(const RefPtr<Component>& component)
40 {
41 auto slider = AceType::DynamicCast<SliderComponent>(component);
42 if (!slider) {
43 LOGE("Update error, slider component is null");
44 return;
45 }
46 if (slider->GetOnChange()) {
47 onChange_ = *slider->GetOnChange();
48 }
49 sliderComponent_ = slider;
50 hoverAnimationType_ = slider->GetMouseAnimationType();
51 if (!blockActive_) {
52 Initialize(slider);
53 if (!slider) {
54 LOGE("RenderSlider update with nullptr");
55 EventReport::SendRenderException(RenderExcepType::RENDER_COMPONENT_ERR);
56 return;
57 }
58 showSteps_ = slider->NeedShowSteps();
59 showTips_ = slider->NeedShowTips();
60 mode_ = slider->GetSliderMode();
61 min_ = slider->GetMinValue();
62 max_ = slider->GetMaxValue();
63 step_ = slider->GetStep();
64 disable_ = slider->GetDisable();
65 SetTextDirection(slider->GetTextDirection());
66 direction_ = slider->GetDirection();
67 isReverse_ = slider->IsReverse();
68 isError_ = false;
69 isValueError_ = false;
70 if (slider->GetThickness().IsValid()) {
71 thickness_ = NormalizeToPx(slider->GetThickness());
72 } else {
73 thickness_ = mode_ == SliderMode::INSET ? NormalizeToPx(DEFAULT_INSET_TRACK_THICKNESS) :
74 NormalizeToPx(DEFAULT_OUTSET_TRACK_THICKNESS);
75 }
76 scaleValue_ = mode_ == SliderMode::INSET ? thickness_ / NormalizeToPx(DEFAULT_INSET_TRACK_THICKNESS) :
77 thickness_ / NormalizeToPx(DEFAULT_OUTSET_TRACK_THICKNESS);
78 SyncValueToComponent(std::clamp(slider->GetValue(), min_, max_));
79
80 ApplyRestoreInfo();
81 slider->SetCurrentValue(value_);
82
83 if (min_ >= max_ || step_ > (max_ - min_) || step_ <= 0.0) {
84 isValueError_ = true;
85 LOGE("RenderSlider update min, max, value, step error");
86 MarkNeedLayout();
87 return;
88 }
89 totalRatio_ = (value_ - min_) / (max_ - min_);
90
91 // Event update
92 if (!slider->GetOnMoveEndEventId().IsEmpty()) {
93 onMoveEnd_ = AceAsyncEvent<void(const std::string&)>::Create(slider->GetOnMoveEndEventId(), context_);
94 }
95 if (!slider->GetOnMovingEventId().IsEmpty()) {
96 onMoving_ = AceAsyncEvent<void(const std::string&)>::Create(slider->GetOnMovingEventId(), context_);
97 }
98 InitAccessibilityEventListener();
99
100 // animation control
101 if (!controller_) {
102 controller_ = CREATE_ANIMATOR(GetContext());
103 }
104
105 const auto& rotationController = slider->GetRotationController();
106 if (rotationController) {
107 auto weak = AceType::WeakClaim(this);
108 rotationController->SetRequestRotationImpl(weak, context_);
109 }
110
111 MarkNeedLayout();
112 }
113 }
114
PerformLayout()115 void RenderSlider::PerformLayout()
116 {
117 Size size = Measure();
118
119 // Update layout size.
120 SetLayoutSize(size);
121
122 // The size of child will be set in flutter slider
123 UpdateTouchRegion();
124 }
125
HandleFocus()126 void RenderSlider::HandleFocus()
127 {
128 auto context = context_.Upgrade();
129 if (!context) {
130 LOGE("Pipeline context upgrade fail!");
131 return;
132 }
133 auto block = AceType::DynamicCast<RenderBlock>(block_);
134 auto track = AceType::DynamicCast<RenderTrack>(track_);
135 if (!block || !track) {
136 return;
137 }
138
139 if (GetFocus()) {
140 const double focusPadding = NormalizeToPx(FOCUS_PADDING);
141 if (mode_ == SliderMode::INSET) {
142 const Size focus = Size(trackLength_ + track->GetTrackThickness(), track->GetTrackThickness());
143 context->ShowFocusAnimation(
144 RRect::MakeRRect(Rect(Offset(), focus), focus.Height() * HALF, focus.Height() * HALF), Color::BLUE,
145 track->GetGlobalOffset() - Offset(track->GetTrackThickness() * HALF, 0.0));
146 } else if (mode_ == SliderMode::OUTSET) {
147 const double blockSize = NormalizeToPx(block->GetBlockSize());
148 const Size focus = Size(blockSize, blockSize) + Size(focusPadding, focusPadding);
149 context->ShowFocusAnimation(
150 RRect::MakeRRect(Rect(Offset(), focus), focus.Width() * HALF, focus.Width() * HALF), Color::BLUE,
151 block->GetGlobalOffset() - Offset(focus.Width() * HALF, focus.Width() * HALF));
152 } else {
153 LOGW("invalid mode");
154 }
155 }
156 }
157
OnPaintFinish()158 void RenderSlider::OnPaintFinish()
159 {
160 HandleFocus();
161 UpdateAccessibilityAttr();
162 }
163
UpdateAccessibilityAttr()164 void RenderSlider::UpdateAccessibilityAttr()
165 {
166 // Update text with slider value
167 auto accessibilityNode = GetAccessibilityNode().Upgrade();
168 if (!accessibilityNode) {
169 return;
170 }
171 accessibilityNode->SetText(std::to_string(value_));
172 accessibilityNode->SetAccessibilityValue(value_, min_, max_);
173 auto context = context_.Upgrade();
174 if (context) {
175 AccessibilityEvent sliderEvent;
176 sliderEvent.nodeId = accessibilityNode->GetNodeId();
177 sliderEvent.eventType = "selected";
178 sliderEvent.componentType = "slider";
179 sliderEvent.currentItemIndex = value_;
180 sliderEvent.itemCount = max_ - min_;
181 context->SendEventToAccessibility(sliderEvent);
182 }
183 }
184
InitAccessibilityEventListener()185 void RenderSlider::InitAccessibilityEventListener()
186 {
187 const auto& accessibilityNode = GetAccessibilityNode().Upgrade();
188 if (!accessibilityNode) {
189 return;
190 }
191 accessibilityNode->AddSupportAction(AceAction::ACTION_SCROLL_BACKWARD);
192 accessibilityNode->AddSupportAction(AceAction::ACTION_SCROLL_FORWARD);
193
194 accessibilityNode->SetActionScrollBackward([weakPtr = WeakClaim(this)]() {
195 const auto& slider = weakPtr.Upgrade();
196 if (slider) {
197 slider->HandleScrollUpdate(-1);
198 return true;
199 }
200 return false;
201 });
202
203 accessibilityNode->SetActionScrollForward([weakPtr = WeakClaim(this)]() {
204 const auto& slider = weakPtr.Upgrade();
205 if (slider) {
206 slider->HandleScrollUpdate(1);
207 return true;
208 }
209 return false;
210 });
211 }
212
HandleScrollUpdate(double delta)213 void RenderSlider::HandleScrollUpdate(double delta)
214 {
215 value_ = value_ + (max_ - min_) * CHANGE_RATIO * delta;
216 if (value_ > max_) {
217 value_ = max_;
218 }
219 if (value_ < min_) {
220 value_ = min_;
221 }
222 SyncValueToComponent(value_);
223 if (min_ >= max_) {
224 return;
225 }
226 totalRatio_ = (value_ - min_) / (max_ - min_);
227 UpdateTouchRegion();
228 MarkNeedLayout();
229 FireMovingEvent(SliderEvent::ACCESSIBILITY);
230 }
231
Measure()232 Size RenderSlider::Measure()
233 {
234 if (direction_ == Axis::VERTICAL) {
235 Size layoutConstrainMax = GetLayoutParam().GetMaxSize();
236 LayoutParam childrenLayoutConstrain;
237 if (layoutConstrainMax.Height() == Size::INFINITE_SIZE) {
238 trackLength_ = NormalizeToPx(DEFAULT_SLIDER_WIDTH_DP) - 2 * NormalizeToPx(SLIDER_PADDING_DP);
239 childrenLayoutConstrain.SetMaxSize(
240 Size(NormalizeToPx(DEFAULT_SLIDER_HEIGHT_DP), NormalizeToPx(DEFAULT_SLIDER_WIDTH_DP)));
241 } else {
242 trackLength_ = layoutConstrainMax.Height() - 2 * NormalizeToPx(SLIDER_PADDING_DP);
243 childrenLayoutConstrain.SetMaxSize(Size(NormalizeToPx(DEFAULT_SLIDER_HEIGHT_DP), trackLength_));
244 }
245 for (const auto& item : GetChildren()) {
246 item->Layout(childrenLayoutConstrain);
247 }
248 if (trackLength_ < 0.0) {
249 trackLength_ = 0.0;
250 }
251 return Size(NormalizeToPx(DEFAULT_SLIDER_HEIGHT_DP), layoutConstrainMax.Height());
252 } else {
253 Size layoutConstrainMax = GetLayoutParam().GetMaxSize();
254 LayoutParam childrenLayoutConstrain;
255 if (layoutConstrainMax.Width() == Size::INFINITE_SIZE) {
256 // set the default size to (260dp, 40dp) and length to 160dp
257 trackLength_ = NormalizeToPx(DEFAULT_SLIDER_WIDTH_DP) - 2 * NormalizeToPx(SLIDER_PADDING_DP);
258 childrenLayoutConstrain.SetMaxSize(
259 Size(NormalizeToPx(DEFAULT_SLIDER_WIDTH_DP), NormalizeToPx(DEFAULT_SLIDER_HEIGHT_DP)));
260 } else {
261 trackLength_ = layoutConstrainMax.Width() - 2 * NormalizeToPx(SLIDER_PADDING_DP);
262 childrenLayoutConstrain.SetMaxSize(Size(trackLength_, NormalizeToPx(DEFAULT_SLIDER_HEIGHT_DP)));
263 }
264 for (const auto& item : GetChildren()) {
265 item->Layout(childrenLayoutConstrain);
266 }
267 if (trackLength_ < 0.0) {
268 trackLength_ = 0.0;
269 }
270 return Size(layoutConstrainMax.Width(), NormalizeToPx(DEFAULT_SLIDER_HEIGHT_DP));
271 }
272 }
273
Initialize(const RefPtr<SliderComponent> & sliderComponent)274 void RenderSlider::Initialize(const RefPtr<SliderComponent>& sliderComponent)
275 {
276 if (sliderComponent && sliderComponent->GetDirection() == Axis::VERTICAL) {
277 dragDetector_ = AceType::MakeRefPtr<VerticalDragRecognizer>();
278 } else {
279 dragDetector_ = AceType::MakeRefPtr<HorizontalDragRecognizer>();
280 }
281 dragDetector_->SetOnDragStart([weakSlider = AceType::WeakClaim(this)](const DragStartInfo& info) {
282 auto slider = weakSlider.Upgrade();
283 if (slider) {
284 slider->HandleDragStart(info.GetLocalLocation());
285 }
286 });
287 dragDetector_->SetOnDragUpdate([weakSlider = AceType::WeakClaim(this)](const DragUpdateInfo& info) {
288 auto slider = weakSlider.Upgrade();
289 if (slider) {
290 slider->HandleDragUpdate(info.GetLocalLocation());
291 }
292 });
293 dragDetector_->SetOnDragEnd([weakSlider = AceType::WeakClaim(this)](const DragEndInfo& info) {
294 auto slider = weakSlider.Upgrade();
295 if (slider) {
296 slider->HandleDragEnd();
297 }
298 });
299 if (!clickDetector_) {
300 clickDetector_ = AceType::MakeRefPtr<ClickRecognizer>();
301 clickDetector_->SetOnClick([weakSlider = AceType::WeakClaim(this)](const ClickInfo& info) {
302 auto slider = weakSlider.Upgrade();
303 if (slider) {
304 slider->HandleClick(info.GetLocalLocation());
305 }
306 });
307 }
308
309 touchDetector_ = AceType::MakeRefPtr<RawRecognizer>();
310 touchDetector_->SetOnTouchDown([weak = AceType::WeakClaim(this)](const TouchEventInfo& info) {
311 if (info.GetTouches().empty()) {
312 return;
313 }
314 auto slider = weak.Upgrade();
315 if (slider) {
316 auto localPosition = info.GetTouches().front().GetLocalLocation();
317 if (slider->blockTouchRegion_.ContainsInRegion(localPosition.GetX(), localPosition.GetY())) {
318 slider->isPress_ = true;
319 slider->MarkNeedLayout();
320 return;
321 }
322 if (slider->NeedSmoothMoving()) {
323 slider->UpdateBlockPosition(localPosition, true);
324 } else {
325 slider->RenderBlockPosition(localPosition);
326 slider->UpdateTouchRegion();
327 }
328 slider->FireMovingEvent(SliderEvent::MOVE_START);
329 }
330 });
331
332 touchDetector_->SetOnTouchUp([weak = AceType::WeakClaim(this)](const TouchEventInfo&) {
333 auto slider = weak.Upgrade();
334 if (slider) {
335 slider->isPress_ = false;
336 slider->MarkNeedLayout();
337 slider->FireMoveEndEvent();
338 }
339 });
340 }
341
AnimateMouseHoverExit()342 void RenderSlider::AnimateMouseHoverExit()
343 {
344 isHover_ = false;
345 MarkNeedLayout();
346 }
347
HandleMouseEvent(const MouseEvent & event)348 bool RenderSlider::HandleMouseEvent(const MouseEvent& event)
349 {
350 auto localPosition = event.GetOffset() - Offset(GetCoordinatePoint().GetX(), GetCoordinatePoint().GetY());
351 if (blockTouchRegion_.ContainsInRegion(localPosition.GetX(), localPosition.GetY())) {
352 isHover_ = true;
353 MarkNeedLayout();
354 } else {
355 isHover_ = false;
356 MarkNeedLayout();
357 }
358 return true;
359 }
360
MouseHoverTest(const Point & parentLocalPoint)361 bool RenderSlider::MouseHoverTest(const Point& parentLocalPoint)
362 {
363 auto context = context_.Upgrade();
364 if (!context) {
365 return false;
366 }
367 if (blockTouchRegion_.ContainsInRegion(
368 parentLocalPoint.GetX() - GetPosition().GetX(), parentLocalPoint.GetY() - GetPosition().GetY())) {
369 if (mouseState_ == MouseState::NONE) {
370 OnMouseHoverEnterTest();
371 mouseState_ = MouseState::HOVER;
372 }
373 context->AddToHoverList(AceType::WeakClaim(this).Upgrade());
374 return true;
375 } else {
376 if (mouseState_ == MouseState::HOVER) {
377 OnMouseHoverExitTest();
378 mouseState_ = MouseState::NONE;
379 }
380 return false;
381 }
382 }
383
FireMoveEndEvent()384 void RenderSlider::FireMoveEndEvent()
385 {
386 if (onMoveEnd_) {
387 std::string param = std::string(R"("changed",{"progress":)")
388 .append(std::to_string(value_))
389 .append(R"(},{"value":)")
390 .append(std::to_string(value_))
391 .append("}");
392 onMoveEnd_(param);
393 }
394 }
395
FireMovingEvent(SliderEvent mode)396 void RenderSlider::FireMovingEvent(SliderEvent mode)
397 {
398 if (onMoving_ || onChange_) {
399 auto jsonResult = JsonUtil::Create(true);
400 jsonResult->Put("progress", std::to_string(value_).c_str());
401 switch (mode) {
402 case SliderEvent::MOVE_START:
403 jsonResult->Put("isEnd", "false");
404 jsonResult->Put("mode", "start");
405 if (onChange_) {
406 onChange_(value_, static_cast<int>(SliderEvent::MOVE_START));
407 }
408 break;
409 case SliderEvent::MOVE_MOVING:
410 jsonResult->Put("isEnd", "false");
411 jsonResult->Put("mode", "move");
412 if (onChange_ && !NearEqual(value_, preMovingValue_)) {
413 onChange_(value_, static_cast<int>(SliderEvent::MOVE_MOVING));
414 preMovingValue_ = value_;
415 }
416 break;
417 case SliderEvent::MOVE_END:
418 jsonResult->Put("isEnd", "true");
419 jsonResult->Put("mode", "end");
420 if (onChange_) {
421 onChange_(value_, static_cast<int>(SliderEvent::MOVE_END));
422 }
423 break;
424 case SliderEvent::CLICK:
425 jsonResult->Put("isEnd", "true");
426 jsonResult->Put("mode", "click");
427 if (onChange_) {
428 onChange_(value_, static_cast<int>(SliderEvent::CLICK));
429 }
430 break;
431 case SliderEvent::ACCESSIBILITY:
432 jsonResult->Put("isEnd", "false");
433 jsonResult->Put("mode", "accessibility");
434 if (onChange_) {
435 onChange_(value_, static_cast<int>(SliderEvent::ACCESSIBILITY));
436 }
437 break;
438 case SliderEvent::FOCUS:
439 jsonResult->Put("isEnd", "true");
440 jsonResult->Put("mode", "keyevent");
441 if (onChange_) {
442 onChange_(value_, static_cast<int>(SliderEvent::FOCUS));
443 }
444 break;
445 default:
446 break;
447 }
448 jsonResult->Put("value", value_);
449 if (onMoving_) {
450 onMoving_(std::string(R"("change",)").append(jsonResult->ToString()));
451 }
452 }
453 }
454
HandleClick(const Offset & clickPosition)455 void RenderSlider::HandleClick(const Offset& clickPosition)
456 {
457 if (NearZero(trackLength_)) {
458 totalRatio_ = 0.0;
459 return;
460 }
461 std::string accessibilityEventType = "click";
462 SendAccessibilityEvent(accessibilityEventType);
463 if (NeedSmoothMoving()) {
464 UpdateBlockPosition(clickPosition, true);
465 } else {
466 RenderBlockPosition(clickPosition);
467 UpdateTouchRegion();
468 }
469 insideBlockRegion_ = false;
470 FireMovingEvent(SliderEvent::CLICK);
471 }
472
HandleDragStart(const Offset & startPoint)473 void RenderSlider::HandleDragStart(const Offset& startPoint)
474 {
475 if (showTips_ && tip_) {
476 tip_->SetVisible(true);
477 }
478 if (NearZero(trackLength_)) {
479 totalRatio_ = 0.0;
480 return;
481 }
482 if (blockTouchRegion_.ContainsInRegion(startPoint.GetX(), startPoint.GetY())) {
483 insideBlockRegion_ = true;
484 blockActive_ = true;
485 UpdateTouchRegion();
486 if (!controller_->IsStopped()) {
487 controller_->Stop();
488 }
489 UpdateAnimation();
490 controller_->Play();
491 isDragging_ = true;
492 FireMovingEvent(SliderEvent::MOVE_START);
493 }
494 }
495
HandleDragUpdate(const Offset & updatePoint)496 void RenderSlider::HandleDragUpdate(const Offset& updatePoint)
497 {
498 if (NearZero(trackLength_)) {
499 totalRatio_ = 0.0;
500 return;
501 }
502 if (insideBlockRegion_) {
503 RenderBlockPosition(updatePoint);
504 FireMovingEvent(SliderEvent::MOVE_MOVING);
505 }
506 }
507
HandleDragEnd()508 void RenderSlider::HandleDragEnd()
509 {
510 if (isDragging_) {
511 isDragging_ = false;
512 }
513 if (tip_) {
514 tip_->SetVisible(false);
515 }
516 if (NearZero(trackLength_)) {
517 totalRatio_ = 0.0;
518 return;
519 }
520 if (insideBlockRegion_) {
521 MarkNeedLayout();
522 UpdateTouchRegion();
523 }
524 FireMovingEvent(SliderEvent::MOVE_END);
525
526 insideBlockRegion_ = false;
527 blockActive_ = false;
528
529 if (!controller_->IsStopped()) {
530 controller_->Stop();
531 }
532 UpdateAnimation();
533 controller_->Play();
534 }
535
536 // Render the block position after clicking or dragging
RenderBlockPosition(const Offset & touchPosition)537 void RenderSlider::RenderBlockPosition(const Offset& touchPosition)
538 {
539 double diff = 0.0;
540 if (direction_ == Axis::VERTICAL) {
541 diff = isReverse_ ? GetLayoutSize().Height() - touchPosition.GetY() - NormalizeToPx(SLIDER_PADDING_DP) :
542 touchPosition.GetY() - NormalizeToPx(SLIDER_PADDING_DP);
543 } else {
544 if ((GetTextDirection() == TextDirection::LTR &&
545 !isReverse_) || (GetTextDirection() == TextDirection::RTL && isReverse_)) {
546 diff = touchPosition.GetX() - NormalizeToPx(SLIDER_PADDING_DP);
547 } else if ((GetTextDirection() == TextDirection::RTL &&
548 !isReverse_) || (GetTextDirection() == TextDirection::LTR && isReverse_)) {
549 diff = GetLayoutSize().Width() - touchPosition.GetX() - NormalizeToPx(SLIDER_PADDING_DP);
550 }
551 }
552 if (diff < 0.0) {
553 SyncValueToComponent(min_);
554 SetTotalRatio(0.0);
555 MarkNeedLayout();
556 return;
557 }
558 totalRatio_ = diff / trackLength_;
559 if (totalRatio_ > 1.0) {
560 value_ = max_;
561 SetTotalRatio(1.0);
562 } else {
563 if (NearEqual(step_, 0.0)) {
564 // continuous slider
565 value_ = (max_ - min_) * totalRatio_ + min_;
566 } else {
567 // The following line is used to find value which is the multiple of step.
568 // The example shows below
569 // "value < x < value + 0.5 * step --> x = value"
570 // "value + 0.5 * step < x < value + step --> x = value + step"
571 double stepRatio = step_ / (max_ - min_);
572 SetTotalRatio(stepRatio * std::floor((totalRatio_ + HALF * stepRatio) / stepRatio));
573 value_ = (max_ - min_) * totalRatio_ + min_;
574 }
575 }
576 SyncValueToComponent(value_);
577 MarkNeedLayout();
578 }
579
UpdateBlockPosition(const Offset & touchPosition,bool isClick)580 void RenderSlider::UpdateBlockPosition(const Offset& touchPosition, bool isClick)
581 {
582 if (LessOrEqual(trackLength_, 0.0)) {
583 LOGE("slider parameter trackLength_ invalid");
584 return;
585 }
586 double diff = 0.0;
587 if (direction_ == Axis::VERTICAL) {
588 diff = isReverse_ ? GetLayoutSize().Height() - touchPosition.GetY() - NormalizeToPx(SLIDER_PADDING_DP) :
589 touchPosition.GetY() - NormalizeToPx(SLIDER_PADDING_DP);
590 } else {
591 if ((GetTextDirection() == TextDirection::LTR &&
592 !isReverse_) || (GetTextDirection() == TextDirection::RTL && isReverse_)) {
593 diff = touchPosition.GetX() - NormalizeToPx(SLIDER_PADDING_DP);
594 } else if ((GetTextDirection() == TextDirection::RTL &&
595 !isReverse_) || (GetTextDirection() == TextDirection::LTR && isReverse_)) {
596 diff = GetLayoutSize().Width() - touchPosition.GetX() - NormalizeToPx(SLIDER_PADDING_DP);
597 }
598 }
599 double totalRatio = diff / trackLength_;
600 if (LessOrEqual(diff, 0.0)) {
601 value_ = min_;
602 SetTotalRatio(0.0);
603 } else if (GreatOrEqual(totalRatio, 1.0)) {
604 value_ = max_;
605 SetTotalRatio(1.0);
606 } else {
607 double stepRatio = step_ / (max_ - min_);
608 double endRatio = stepRatio * std::floor((totalRatio + HALF * stepRatio) / stepRatio);
609 SetTotalRatio(endRatio);
610 value_ = (max_ - min_) * endRatio + min_;
611 if (GreatOrEqual(value_, max_)) {
612 value_ = max_;
613 }
614 }
615 RestartMoveAnimation(value_, isClick);
616 }
617
UpdateTipText(double value)618 void RenderSlider::UpdateTipText(double value)
619 {
620 int32_t percent = std::round(value * DOUBLE_TO_PERCENT);
621 std::string valueText = std::to_string(percent).append("%");
622 if (tipText_ && renderText_) {
623 tipText_->SetData(valueText);
624 renderText_->Update(tipText_);
625 renderText_->PerformLayout();
626 }
627 }
628
OnTouchTestHit(const Offset & coordinateOffset,const TouchRestrict & touchRestrict,TouchTestResult & result)629 void RenderSlider::OnTouchTestHit(
630 const Offset& coordinateOffset, const TouchRestrict& touchRestrict, TouchTestResult& result)
631 {
632 if (!isValueError_ && !disable_) {
633 dragDetector_->SetCoordinateOffset(coordinateOffset);
634 clickDetector_->SetCoordinateOffset(coordinateOffset);
635 touchDetector_->SetCoordinateOffset(coordinateOffset);
636 result.emplace_back(dragDetector_);
637 result.emplace_back(clickDetector_);
638 result.emplace_back(touchDetector_);
639 }
640 }
641
FindCenterVertex(double x,double y,double objectWidth,double objectHeight)642 Vertex RenderSlider::FindCenterVertex(double x, double y, double objectWidth, double objectHeight)
643 {
644 // 0.5 is used to find the center position.
645 return Vertex(x + objectWidth * HALF, y + objectHeight * HALF);
646 }
647
GetTopTouchRegion(const Vertex & center,double width,double height)648 TouchRegionPoint RenderSlider::GetTopTouchRegion(const Vertex& center, double width, double height)
649 {
650 // 0.5 is used to find the top left point of the touch region
651 return TouchRegionPoint(center.GetX() - width * HALF, center.GetY() - height * HALF);
652 }
653
GetBotTouchRegion(const Vertex & center,double width,double height)654 TouchRegionPoint RenderSlider::GetBotTouchRegion(const Vertex& center, double width, double height)
655 {
656 // 0.5 is used to find the bot right point of the touch region
657 return TouchRegionPoint(center.GetX() + width * HALF, center.GetY() + height * HALF);
658 }
659
UpdateTouchRegion()660 void RenderSlider::UpdateTouchRegion()
661 {
662 if (direction_ == Axis::VERTICAL) {
663 double dxOffset = GetLayoutSize().Width() * HALF;
664 double dyOffset = trackLength_ * totalRatio_ + NormalizeToPx(SLIDER_PADDING_DP);
665 Vertex blockCenter = isReverse_ ?
666 TouchRegionPoint(dxOffset, GetLayoutSize().Height() - dyOffset) : TouchRegionPoint(dxOffset, dyOffset);
667 TouchRegionPoint blockTopPoint =
668 GetTopTouchRegion(blockCenter, NormalizeToPx(blockHotWidth_), NormalizeToPx(blockHotHeight_));
669 TouchRegionPoint blockBottomPoint =
670 GetBotTouchRegion(blockCenter, NormalizeToPx(blockHotWidth_), NormalizeToPx(blockHotHeight_));
671 blockTouchRegion_ = TouchRegion(blockTopPoint, blockBottomPoint);
672 } else {
673 double dxOffset = trackLength_ * totalRatio_ + NormalizeToPx(SLIDER_PADDING_DP);
674 double dyOffset = GetLayoutSize().Height() * HALF;
675 Vertex blockCenter = TouchRegionPoint();
676 if ((GetTextDirection() == TextDirection::LTR &&
677 !isReverse_) || (GetTextDirection() == TextDirection::RTL && isReverse_)) {
678 blockCenter = TouchRegionPoint(dxOffset, dyOffset);
679 } else if ((GetTextDirection() == TextDirection::RTL &&
680 !isReverse_) || (GetTextDirection() == TextDirection::LTR && isReverse_)) {
681 blockCenter = TouchRegionPoint(GetLayoutSize().Width() - dxOffset, dyOffset);
682 }
683 TouchRegionPoint blockTopPoint =
684 GetTopTouchRegion(blockCenter, NormalizeToPx(blockHotWidth_), NormalizeToPx(blockHotHeight_));
685 TouchRegionPoint blockBottomPoint =
686 GetBotTouchRegion(blockCenter, NormalizeToPx(blockHotWidth_), NormalizeToPx(blockHotHeight_));
687 blockTouchRegion_ = TouchRegion(blockTopPoint, blockBottomPoint);
688 }
689 }
690
HandleFocusEvent(const KeyEvent & keyEvent)691 bool RenderSlider::HandleFocusEvent(const KeyEvent& keyEvent)
692 {
693 bool updateEvent = false;
694 if (NearZero(trackLength_)) {
695 totalRatio_ = 0.0;
696 return updateEvent;
697 }
698 switch (keyEvent.code) {
699 case KeyCode::TV_CONTROL_LEFT:
700 totalRatio_ -= step_ / (max_ - min_);
701 if (totalRatio_ < 0.0) {
702 totalRatio_ = 0.0;
703 }
704 SyncValueToComponent((max_ - min_) * totalRatio_ + min_);
705 MarkNeedLayout();
706 updateEvent = true;
707 break;
708 case KeyCode::TV_CONTROL_RIGHT:
709 totalRatio_ += step_ / (max_ - min_);
710 if (totalRatio_ > 1.0) {
711 totalRatio_ = 1.0;
712 }
713 SyncValueToComponent((max_ - min_) * totalRatio_ + min_);
714 MarkNeedLayout();
715 updateEvent = true;
716 break;
717 default:
718 updateEvent = false;
719 break;
720 }
721 if (updateEvent) {
722 FireMoveEndEvent();
723 FireMovingEvent(SliderEvent::FOCUS);
724 std::string accessibilityEventType = "focus";
725 SendAccessibilityEvent(accessibilityEventType);
726 }
727 return updateEvent;
728 }
729
StartMoveAnimation(double from,double to,bool isClick)730 void RenderSlider::StartMoveAnimation(double from, double to, bool isClick)
731 {
732 if (NearEqual(from, to)) {
733 return;
734 }
735 if (!moveController_) {
736 moveController_ = CREATE_ANIMATOR(GetContext());
737 } else if (moveController_->IsRunning()) {
738 moveController_->Finish();
739 }
740 moveController_->ClearInterpolators();
741 moveController_->ClearAllListeners();
742
743 moveController_->AddStartListener([weak = AceType::WeakClaim(this), to]() {
744 auto slider = weak.Upgrade();
745 if (slider) {
746 slider->animationEnd_ = to;
747 }
748 });
749
750 moveController_->AddStopListener([weak = AceType::WeakClaim(this)]() {
751 auto slider = weak.Upgrade();
752 if (slider) {
753 slider->SyncValueToComponent(slider->totalRatio_ * (slider->max_ - slider->min_) + slider->min_);
754 slider->SetTotalRatio(slider->totalRatio_);
755 slider->UpdateTouchRegion();
756 }
757 });
758
759 ResetMoveAnimation(from, to);
760 moveController_->SetDuration(SLIDER_MOVE_DURATION);
761 moveController_->AddInterpolator(moveAnimation_);
762 moveController_->Play();
763 }
764
CalculateTotalRadio()765 void RenderSlider::CalculateTotalRadio()
766 {
767 auto ratio = (value_ - min_) / (max_ - min_);
768 totalRatio_ = std::clamp(ratio, 0.0, 1.0);
769 }
770
ResetMoveAnimation(double from,double to)771 void RenderSlider::ResetMoveAnimation(double from, double to)
772 {
773 moveAnimation_ = AceType::MakeRefPtr<CurveAnimation<double>>(from, to, Curves::MAGNETIC);
774 auto weak = AceType::WeakClaim(this);
775 moveAnimation_->AddListener(Animation<double>::ValueCallback([weak](double value) {
776 auto slider = weak.Upgrade();
777 if (slider) {
778 slider->value_ = value;
779 slider->CalculateTotalRadio();
780 slider->MarkNeedLayout();
781 }
782 }));
783 }
784
RestartMoveAnimation(double value,bool isClick)785 void RenderSlider::RestartMoveAnimation(double value, bool isClick)
786 {
787 if (moveController_ && moveController_->IsRunning()) {
788 if (!NearEqual(value, animationEnd_)) {
789 moveController_->Stop();
790 StartMoveAnimation(value_, value, isClick);
791 }
792 } else {
793 StartMoveAnimation(value_, value, isClick);
794 }
795 }
796
UpdateAnimation()797 void RenderSlider::UpdateAnimation()
798 {
799 double from = DEFAULT_NORMAL_RADIUS_SCALE;
800 double to = DEFAULT_LARGE_RADIUS_SCALE;
801 if (!blockActive_) {
802 from = DEFAULT_LARGE_RADIUS_SCALE;
803 to = DEFAULT_NORMAL_RADIUS_SCALE;
804 }
805
806 if (translate_) {
807 controller_->RemoveInterpolator(translate_);
808 }
809 translate_ = AceType::MakeRefPtr<CurveAnimation<double>>(from, to, Curves::FRICTION);
810 auto weak = AceType::WeakClaim(this);
811 translate_->AddListener(Animation<double>::ValueCallback([weak](double value) {
812 auto sliderComp = weak.Upgrade();
813 if (sliderComp) {
814 sliderComp->radiusScale_ = value;
815 sliderComp->MarkNeedLayout();
816 }
817 }));
818 controller_->SetDuration(DEFAULT_SLIDER_ANIMATION_DURATION);
819 controller_->AddInterpolator(translate_);
820 }
821
ProvideRestoreInfo()822 std::string RenderSlider::ProvideRestoreInfo()
823 {
824 auto jsonObj = JsonUtil::Create(true);
825 jsonObj->Put("value", value_);
826 jsonObj->Put("showTips", showTips_);
827 jsonObj->Put("showSteps", showSteps_);
828 jsonObj->Put("thickness", thickness_);
829 jsonObj->Put("min", min_);
830 jsonObj->Put("max", max_);
831 jsonObj->Put("step", step_);
832 return jsonObj->ToString();
833 }
834
ApplyRestoreInfo()835 void RenderSlider::ApplyRestoreInfo()
836 {
837 if (GetRestoreInfo().empty()) {
838 return;
839 }
840 auto info = JsonUtil::ParseJsonString(GetRestoreInfo());
841 if (!info->IsValid() || !info->IsObject()) {
842 LOGW("RenderSlider:: restore info is invalid");
843 return;
844 }
845
846 auto jsonValue = info->GetValue("value");
847 auto jsonShowTips = info->GetValue("showTips");
848 auto jsonShowSteps = info->GetValue("showSteps");
849 auto jsonThickness = info->GetValue("thickness");
850 auto jsonMin = info->GetValue("min");
851 auto jsonMax = info->GetValue("max");
852 auto jsonStep = info->GetValue("step");
853
854 value_ = jsonValue->GetDouble();
855 showTips_ = jsonShowTips->GetBool();
856 showSteps_ = jsonShowSteps->GetBool();
857 thickness_ = jsonThickness->GetDouble();
858 min_ = jsonMin->GetDouble();
859 max_ = jsonMax->GetDouble();
860 step_ = jsonStep->GetDouble();
861
862 SetRestoreInfo("");
863 }
864
OnRotation(const RotationEvent & event)865 bool RenderSlider::OnRotation(const RotationEvent& event)
866 {
867 double eventValue = -1 * event.value;
868 if (GreatNotEqual(eventValue, 0)) {
869 HandleScrollUpdate(INTERMITTENT_RATIO);
870 } else if (LessNotEqual(eventValue, 0)) {
871 HandleScrollUpdate(-INTERMITTENT_RATIO);
872 }
873 return true;
874 }
875
876 } // namespace OHOS::Ace
877