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