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