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