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