1 /*
2 * Copyright (c) 2022-2023 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_ng/pattern/scroll/inner/scroll_bar.h"
17
18 #include "base/utils/utils.h"
19 #include "core/animation/curve_animation.h"
20 #include "core/animation/curves.h"
21 #include "core/components/scroll/scroll_bar_theme.h"
22 #include "core/pipeline_ng/pipeline_context.h"
23
24 namespace OHOS::Ace::NG {
25 namespace {
26 constexpr int32_t STOP_DURATION = 2000; // 2000ms
27 constexpr float KEY_TIME_START = 0.0f;
28 constexpr float KEY_TIME_MIDDLE = 0.7f;
29 constexpr float KEY_TIME_END = 1.0f;
30 constexpr int32_t BAR_EXPAND_DURATION = 150; // 150ms, scroll bar width expands from 4dp to 8dp
31 constexpr int32_t BAR_SHRINK_DURATION = 250; // 250ms, scroll bar width shrinks from 8dp to 4dp
32 constexpr int32_t BAR_ADAPT_DURATION = 400; // 400ms, scroll bar adapts to the size changes of components
33 constexpr double BAR_ADAPT_EPSLION = 1.0;
34 } // namespace
35
ScrollBar()36 ScrollBar::ScrollBar()
37 {
38 InitTheme();
39 }
40
ScrollBar(DisplayMode displayMode,ShapeMode shapeMode,PositionMode positionMode)41 ScrollBar::ScrollBar(DisplayMode displayMode, ShapeMode shapeMode, PositionMode positionMode)
42 : displayMode_(displayMode), shapeMode_(shapeMode), positionMode_(positionMode)
43 {
44 InitTheme();
45 }
46
InitTheme()47 void ScrollBar::InitTheme()
48 {
49 auto pipelineContext = PipelineContext::GetCurrentContext();
50 CHECK_NULL_VOID(pipelineContext);
51 auto theme = pipelineContext->GetTheme<ScrollBarTheme>();
52 CHECK_NULL_VOID(theme);
53 SetInactiveWidth(theme->GetNormalWidth());
54 SetNormalWidth(theme->GetNormalWidth());
55 SetActiveWidth(theme->GetActiveWidth());
56 SetTouchWidth(theme->GetTouchWidth());
57 SetReservedHeight(theme->GetReservedHeight());
58 SetMinHeight(theme->GetMinHeight());
59 SetMinDynamicHeight(theme->GetMinDynamicHeight());
60 SetBackgroundColor(theme->GetBackgroundColor());
61 SetForegroundColor(theme->GetForegroundColor());
62 SetPadding(theme->GetPadding());
63 SetScrollable(true);
64 }
65
InBarTouchRegion(const Point & point) const66 bool ScrollBar::InBarTouchRegion(const Point& point) const
67 {
68 if (NeedScrollBar() && shapeMode_ == ShapeMode::RECT) {
69 return touchRegion_.IsInRegion(point);
70 }
71 return false;
72 }
73
InBarActiveRegion(const Point & point) const74 bool ScrollBar::InBarActiveRegion(const Point& point) const
75 {
76 if (NeedScrollBar() && shapeMode_ == ShapeMode::RECT) {
77 return activeRect_.IsInRegion(point);
78 }
79 return false;
80 }
81
FlushBarWidth()82 void ScrollBar::FlushBarWidth()
83 {
84 SetBarRegion(paintOffset_, viewPortSize_);
85 if (shapeMode_ == ShapeMode::RECT) {
86 SetRectTrickRegion(paintOffset_, viewPortSize_, lastOffset_, estimatedHeight_);
87 } else {
88 SetRoundTrickRegion(paintOffset_, viewPortSize_, lastOffset_, estimatedHeight_);
89 }
90 }
91
UpdateScrollBarRegion(const Offset & offset,const Size & size,const Offset & lastOffset,double estimatedHeight)92 void ScrollBar::UpdateScrollBarRegion(
93 const Offset& offset, const Size& size, const Offset& lastOffset, double estimatedHeight)
94 {
95 // return if nothing changes to avoid changing opacity
96 if (!positionModeUpdate_ && paintOffset_ == offset && viewPortSize_ == size && lastOffset_ == lastOffset &&
97 NearEqual(estimatedHeight_, estimatedHeight, 0.000001f)) {
98 return;
99 }
100 if (!NearZero(estimatedHeight)) {
101 paintOffset_ = offset;
102 viewPortSize_ = size;
103 lastOffset_ = lastOffset;
104 estimatedHeight_ = estimatedHeight;
105 opacity_ = UINT8_MAX;
106 SetBarRegion(offset, size);
107 if (shapeMode_ == ShapeMode::RECT) {
108 SetRectTrickRegion(offset, size, lastOffset, estimatedHeight);
109 } else {
110 SetRoundTrickRegion(offset, size, lastOffset, estimatedHeight);
111 }
112 positionModeUpdate_ = false;
113 }
114 OnScrollEnd();
115 }
116
UpdateActiveRectSize(double activeSize)117 void ScrollBar::UpdateActiveRectSize(double activeSize)
118 {
119 if (positionMode_ == PositionMode::LEFT || positionMode_ == PositionMode::RIGHT) {
120 activeRect_.SetHeight(activeSize);
121 touchRegion_.SetHeight(activeSize);
122 } else if (positionMode_ == PositionMode::BOTTOM) {
123 activeRect_.SetWidth(activeSize);
124 touchRegion_.SetWidth(activeSize);
125 }
126 }
127
UpdateActiveRectOffset(double activeMainOffset)128 void ScrollBar::UpdateActiveRectOffset(double activeMainOffset)
129 {
130 if (positionMode_ == PositionMode::LEFT || positionMode_ == PositionMode::RIGHT) {
131 activeMainOffset = std::min(activeMainOffset, barRegionSize_ - activeRect_.Height());
132 activeRect_.SetTop(activeMainOffset);
133 touchRegion_.SetTop(activeMainOffset);
134 } else if (positionMode_ == PositionMode::BOTTOM) {
135 activeMainOffset = std::min(activeMainOffset, barRegionSize_ - activeRect_.Width());
136 activeRect_.SetLeft(activeMainOffset);
137 touchRegion_.SetLeft(activeMainOffset);
138 }
139 }
140
SetBarRegion(const Offset & offset,const Size & size)141 void ScrollBar::SetBarRegion(const Offset& offset, const Size& size)
142 {
143 double normalWidth = NormalizeToPx(normalWidth_);
144 if (shapeMode_ == ShapeMode::RECT) {
145 double height = std::max(size.Height() - NormalizeToPx(reservedHeight_), 0.0);
146 if (positionMode_ == PositionMode::LEFT) {
147 barRect_ = Rect(0.0, 0.0, normalWidth, height) + offset;
148 } else if (positionMode_ == PositionMode::RIGHT) {
149 barRect_ =
150 Rect(size.Width() - normalWidth - NormalizeToPx(padding_.Right()), 0.0, normalWidth, height) + offset;
151 } else if (positionMode_ == PositionMode::BOTTOM) {
152 auto scrollBarWidth = std::max(size.Width() - NormalizeToPx(reservedHeight_), 0.0);
153 barRect_ =
154 Rect(0.0, size.Height() - normalWidth - NormalizeToPx(padding_.Bottom()), scrollBarWidth, normalWidth) +
155 offset;
156 }
157 }
158 }
159
SetRectTrickRegion(const Offset & offset,const Size & size,const Offset & lastOffset,double estimatedHeight)160 void ScrollBar::SetRectTrickRegion(
161 const Offset& offset, const Size& size, const Offset& lastOffset, double estimatedHeight)
162 {
163 double mainSize = (positionMode_ == PositionMode::BOTTOM ? size.Width() : size.Height());
164 barRegionSize_ = std::max(mainSize - NormalizeToPx(reservedHeight_), 0.0);
165 if (LessOrEqual(estimatedHeight, 0.0)) {
166 return;
167 }
168 double activeSize = barRegionSize_ * mainSize / estimatedHeight - outBoundary_;
169 if (!NearEqual(mainSize, estimatedHeight)) {
170 if (!NearZero(outBoundary_)) {
171 activeSize = std::max(
172 std::max(activeSize, NormalizeToPx(minHeight_) - outBoundary_), NormalizeToPx(minDynamicHeight_));
173 } else {
174 activeSize = std::max(activeSize, NormalizeToPx(minHeight_));
175 }
176 double normalWidth = NormalizeToPx(normalWidth_);
177 if (LessOrEqual(activeSize, normalWidth)) {
178 activeSize = normalWidth;
179 }
180 double lastMainOffset =
181 std::max(positionMode_ == PositionMode::BOTTOM ? lastOffset.GetX() : lastOffset.GetY(), 0.0);
182 offsetScale_ = (barRegionSize_ - activeSize) / (estimatedHeight - mainSize);
183 double activeMainOffset = offsetScale_ * lastMainOffset;
184 bool canUseAnimation = !inSpring && !positionModeUpdate_;
185 activeMainOffset = std::min(activeMainOffset, barRegionSize_ - activeSize);
186 double inactiveSize = 0.0;
187 double inactiveMainOffset = 0.0;
188 scrollableOffset_ = activeMainOffset;
189 if (positionMode_ == PositionMode::LEFT) {
190 inactiveSize = activeRect_.Height();
191 inactiveMainOffset = activeRect_.Top();
192 if (adaptAnimator_ && adaptAnimator_->IsRunning()) {
193 activeRect_ =
194 Rect(-NormalizeToPx(position_), activeRect_.Top(), normalWidth, activeRect_.Height()) + offset;
195 } else {
196 activeRect_ = Rect(-NormalizeToPx(position_), activeMainOffset, normalWidth, activeSize) + offset;
197 }
198 touchRegion_ = activeRect_ + Size(NormalizeToPx(touchWidth_), 0);
199 } else if (positionMode_ == PositionMode::RIGHT) {
200 inactiveSize = activeRect_.Height();
201 inactiveMainOffset = activeRect_.Top();
202 double x = size.Width() - normalWidth - NormalizeToPx(padding_.Right()) + NormalizeToPx(position_);
203 if (adaptAnimator_ && adaptAnimator_->IsRunning()) {
204 activeRect_ = Rect(x, activeRect_.Top(), normalWidth, activeRect_.Height()) + offset;
205 } else {
206 activeRect_ = Rect(x, activeMainOffset, normalWidth, activeSize) + offset;
207 }
208 // Update the hot region
209 touchRegion_ =
210 activeRect_ -
211 Offset(
212 NormalizeToPx(touchWidth_) - NormalizeToPx(normalWidth_) - NormalizeToPx(padding_.Right()), 0.0) +
213 Size(NormalizeToPx(touchWidth_) - NormalizeToPx(normalWidth_), 0);
214 } else if (positionMode_ == PositionMode::BOTTOM) {
215 inactiveSize = activeRect_.Width();
216 inactiveMainOffset = activeRect_.Left();
217 auto positionY = size.Height() - normalWidth - NormalizeToPx(padding_.Bottom()) + NormalizeToPx(position_);
218 if (adaptAnimator_ && adaptAnimator_->IsRunning()) {
219 activeRect_ = Rect(activeRect_.Left(), positionY, activeRect_.Width(), normalWidth) + offset;
220 } else {
221 activeRect_ = Rect(activeMainOffset, positionY, activeSize, normalWidth) + offset;
222 }
223 auto hotRegionOffset = Offset(
224 0.0, NormalizeToPx(touchWidth_) - NormalizeToPx(normalWidth_) - NormalizeToPx(padding_.Bottom()));
225 auto hotRegionSize = Size(0, NormalizeToPx(touchWidth_) - NormalizeToPx(normalWidth_));
226 touchRegion_ = activeRect_ - hotRegionOffset + hotRegionSize;
227 }
228 // If the scrollBar length changes, start the adaptation animation
229 if (!NearZero(inactiveSize) && !NearEqual(activeSize, inactiveSize, BAR_ADAPT_EPSLION) && canUseAnimation) {
230 PlayAdaptAnimation(activeSize, activeMainOffset, inactiveSize, inactiveMainOffset);
231 }
232 }
233 }
234
SetRoundTrickRegion(const Offset & offset,const Size & size,const Offset & lastOffset,double estimatedHeight)235 void ScrollBar::SetRoundTrickRegion(
236 const Offset& offset, const Size& size, const Offset& lastOffset, double estimatedHeight)
237 {
238 double diameter = std::min(size.Width(), size.Height());
239 if (!NearEqual(estimatedHeight, diameter)) {
240 double maxAngle = bottomAngle_ - topAngle_;
241 trickSweepAngle_ = std::max(diameter * maxAngle / estimatedHeight, minAngle_);
242 double lastOffsetY = std::max(lastOffset.GetY(), 0.0);
243 double trickStartAngle = (maxAngle - trickSweepAngle_) * lastOffsetY / (estimatedHeight - diameter);
244 trickStartAngle = std::clamp(0.0, trickStartAngle, maxAngle) - maxAngle * FACTOR_HALF;
245 if (positionMode_ == PositionMode::LEFT) {
246 if (trickStartAngle > 0.0) {
247 trickStartAngle_ = STRAIGHT_ANGLE - trickStartAngle;
248 } else {
249 trickStartAngle_ = -(trickStartAngle + STRAIGHT_ANGLE);
250 }
251 trickSweepAngle_ = -trickSweepAngle_;
252 } else {
253 trickStartAngle_ = trickStartAngle;
254 }
255 }
256 }
257
NeedScrollBar() const258 bool ScrollBar::NeedScrollBar() const
259 {
260 return displayMode_ == DisplayMode::AUTO || displayMode_ == DisplayMode::ON;
261 }
262
NeedPaint() const263 bool ScrollBar::NeedPaint() const
264 {
265 return NeedScrollBar() && isScrollable_ && GreatNotEqual(normalWidth_.Value(), 0.0) && opacity_ > 0;
266 }
267
GetNormalWidthToPx() const268 double ScrollBar::GetNormalWidthToPx() const
269 {
270 return NormalizeToPx(normalWidth_);
271 }
272
CalcPatternOffset(float scrollBarOffset) const273 float ScrollBar::CalcPatternOffset(float scrollBarOffset) const
274 {
275 if (!isDriving_ || NearZero(offsetScale_)) {
276 return scrollBarOffset;
277 }
278 return -scrollBarOffset / offsetScale_;
279 }
280
NormalizeToPx(const Dimension & dimension) const281 double ScrollBar::NormalizeToPx(const Dimension& dimension) const
282 {
283 auto pipelineContext = PipelineContext::GetCurrentContext();
284 CHECK_NULL_RETURN(pipelineContext, 0.0);
285 return pipelineContext->NormalizeToPx(dimension);
286 }
287
SetGestureEvent()288 void ScrollBar::SetGestureEvent()
289 {
290 if (!touchEvent_) {
291 touchEvent_ = MakeRefPtr<TouchEventImpl>([weak = WeakClaim(this)](const TouchEventInfo& info) {
292 auto scrollBar = weak.Upgrade();
293 CHECK_NULL_VOID(scrollBar);
294 if (info.GetTouches().empty()) {
295 return;
296 }
297 auto touch = info.GetTouches().front();
298 if (touch.GetTouchType() == TouchType::DOWN) {
299 Point point(touch.GetLocalLocation().GetX(), touch.GetLocalLocation().GetY());
300 bool inTouchRegion = scrollBar->InBarTouchRegion(point);
301 scrollBar->SetPressed(inTouchRegion);
302 scrollBar->SetDriving(inTouchRegion);
303 if (inTouchRegion && !scrollBar->IsHover()) {
304 scrollBar->PlayGrowAnimation();
305 }
306 if (scrollBar->scrollEndAnimator_ && !scrollBar->scrollEndAnimator_->IsStopped()) {
307 scrollBar->scrollEndAnimator_->Stop();
308 }
309 scrollBar->MarkNeedRender();
310 }
311 if (info.GetTouches().front().GetTouchType() == TouchType::UP) {
312 if (scrollBar->IsPressed() && !scrollBar->IsHover()) {
313 scrollBar->PlayShrinkAnimation();
314 }
315 scrollBar->SetPressed(false);
316 scrollBar->MarkNeedRender();
317 }
318 });
319 }
320 if (!touchAnimator_) {
321 touchAnimator_ = AceType::MakeRefPtr<Animator>(PipelineContext::GetCurrentContext());
322 }
323 }
324
SetMouseEvent()325 void ScrollBar::SetMouseEvent()
326 {
327 if (mouseEvent_) {
328 return;
329 }
330 mouseEvent_ = MakeRefPtr<InputEvent>([weak = WeakClaim(this)](MouseInfo& info) {
331 auto scrollBar = weak.Upgrade();
332 CHECK_NULL_VOID_NOLOG(scrollBar);
333 Point point(info.GetLocalLocation().GetX(), info.GetLocalLocation().GetY());
334 bool inRegion = scrollBar->InBarActiveRegion(point);
335 if (inRegion && !scrollBar->IsHover()) {
336 if (!scrollBar->IsPressed()) {
337 scrollBar->PlayGrowAnimation();
338 }
339 scrollBar->SetHover(true);
340 if (scrollBar->scrollEndAnimator_ && !scrollBar->scrollEndAnimator_->IsStopped()) {
341 scrollBar->scrollEndAnimator_->Stop();
342 }
343 scrollBar->MarkNeedRender();
344 }
345 if (scrollBar->IsHover() && !inRegion) {
346 if (!scrollBar->IsPressed()) {
347 scrollBar->PlayShrinkAnimation();
348 if (scrollBar->GetDisplayMode() == DisplayMode::AUTO) {
349 scrollBar->PlayBarEndAnimation();
350 }
351 }
352 scrollBar->SetHover(false);
353 scrollBar->MarkNeedRender();
354 }
355 });
356 }
357
PlayAdaptAnimation(double activeSize,double activeMainOffset,double inactiveSize,double inactiveMainOffset)358 void ScrollBar::PlayAdaptAnimation(
359 double activeSize, double activeMainOffset, double inactiveSize, double inactiveMainOffset)
360 {
361 if (adaptAnimator_ && adaptAnimator_->IsRunning()) {
362 return;
363 }
364 if (!adaptAnimator_) {
365 adaptAnimator_ = AceType::MakeRefPtr<Animator>(PipelineContext::GetCurrentContext());
366 }
367 adaptAnimator_->ClearInterpolators();
368 // Animate the mainSize of the ScrollBar
369 auto sizeAnimation = AceType::MakeRefPtr<CurveAnimation<double>>(inactiveSize, activeSize, Curves::FRICTION);
370 sizeAnimation->AddListener([weakBar = AceType::WeakClaim(this)](double value) {
371 auto scrollBar = weakBar.Upgrade();
372 if (scrollBar) {
373 scrollBar->UpdateActiveRectSize(value);
374 scrollBar->MarkNeedRender();
375 }
376 });
377 // Animate the mainOffset of the ScrollBar
378 auto offsetAnimation =
379 AceType::MakeRefPtr<CurveAnimation<double>>(inactiveMainOffset, activeMainOffset, Curves::FRICTION);
380 offsetAnimation->AddListener(
381 [weakBar = AceType::WeakClaim(this), inactiveMainOffset, activeMainOffset](double value) {
382 auto scrollBar = weakBar.Upgrade();
383 if (scrollBar) {
384 auto top = scrollBar->GetPositionMode() == PositionMode::BOTTOM ? scrollBar->activeRect_.Left() :
385 scrollBar->activeRect_.Top();
386 if (NearEqual(top, activeMainOffset, 0.000001f) || NearEqual(top, inactiveMainOffset, 0.000001f)) {
387 scrollBar->UpdateActiveRectOffset(value);
388 } else {
389 scrollBar->UpdateActiveRectOffset(value + scrollBar->scrollableOffset_ - activeMainOffset);
390 }
391 scrollBar->MarkNeedRender();
392 }
393 });
394 adaptAnimator_->AddInterpolator(sizeAnimation);
395 adaptAnimator_->AddInterpolator(offsetAnimation);
396 adaptAnimator_->SetDuration(BAR_ADAPT_DURATION);
397
398 UpdateActiveRectSize(inactiveSize);
399 UpdateActiveRectOffset(inactiveMainOffset);
400
401 adaptAnimator_->Play();
402 }
403
PlayGrowAnimation()404 void ScrollBar::PlayGrowAnimation()
405 {
406 if (!touchAnimator_->IsStopped()) {
407 touchAnimator_->Stop();
408 }
409 touchAnimator_->ClearInterpolators();
410 auto activeWidth = activeWidth_.ConvertToPx();
411 auto inactiveWidth = inactiveWidth_.ConvertToPx();
412
413 auto animation = AceType::MakeRefPtr<CurveAnimation<double>>(inactiveWidth, activeWidth, Curves::SHARP);
414 animation->AddListener([weakBar = AceType::WeakClaim(this)](double value) {
415 auto scrollBar = weakBar.Upgrade();
416 if (scrollBar) {
417 scrollBar->normalWidth_ = Dimension(value, DimensionUnit::PX);
418 scrollBar->FlushBarWidth();
419 scrollBar->MarkNeedRender();
420 }
421 });
422 touchAnimator_->AddInterpolator(animation);
423 touchAnimator_->SetDuration(BAR_EXPAND_DURATION);
424 touchAnimator_->Play();
425 }
426
PlayShrinkAnimation()427 void ScrollBar::PlayShrinkAnimation()
428 {
429 if (!touchAnimator_->IsStopped()) {
430 touchAnimator_->Stop();
431 }
432 touchAnimator_->ClearInterpolators();
433 auto activeWidth = activeWidth_.ConvertToPx();
434 auto inactiveWidth = inactiveWidth_.ConvertToPx();
435
436 auto animation = AceType::MakeRefPtr<CurveAnimation<double>>(activeWidth, inactiveWidth, Curves::SHARP);
437 animation->AddListener([weakBar = AceType::WeakClaim(this)](double value) {
438 auto scrollBar = weakBar.Upgrade();
439 if (scrollBar) {
440 scrollBar->normalWidth_ = Dimension(value, DimensionUnit::PX);
441 scrollBar->FlushBarWidth();
442 scrollBar->MarkNeedRender();
443 }
444 });
445 touchAnimator_->AddInterpolator(animation);
446 touchAnimator_->SetDuration(BAR_SHRINK_DURATION);
447 touchAnimator_->Play();
448 }
449
PlayBarEndAnimation()450 void ScrollBar::PlayBarEndAnimation()
451 {
452 if (scrollEndAnimator_ && !scrollEndAnimator_->IsStopped()) {
453 scrollEndAnimator_->Stop();
454 }
455 if (scrollEndAnimator_) {
456 scrollEndAnimator_->Play();
457 return;
458 }
459
460 scrollEndAnimator_ = AceType::MakeRefPtr<Animator>(PipelineContext::GetCurrentContext());
461 auto hiddenStartKeyframe = AceType::MakeRefPtr<Keyframe<int32_t>>(KEY_TIME_START, UINT8_MAX);
462 auto hiddenMiddleKeyframe = AceType::MakeRefPtr<Keyframe<int32_t>>(KEY_TIME_MIDDLE, UINT8_MAX);
463 auto hiddenEndKeyframe = AceType::MakeRefPtr<Keyframe<int32_t>>(KEY_TIME_END, 0);
464 hiddenMiddleKeyframe->SetCurve(Curves::LINEAR);
465 hiddenEndKeyframe->SetCurve(Curves::FRICTION);
466
467 auto animation = AceType::MakeRefPtr<KeyframeAnimation<int32_t>>();
468 animation->AddKeyframe(hiddenStartKeyframe);
469 animation->AddKeyframe(hiddenMiddleKeyframe);
470 animation->AddKeyframe(hiddenEndKeyframe);
471 animation->AddListener([weakBar = AceType::WeakClaim(this)](int32_t value) {
472 auto scrollBar = weakBar.Upgrade();
473 if (scrollBar) {
474 scrollBar->opacity_ = value;
475 scrollBar->MarkNeedRender();
476 }
477 });
478 scrollEndAnimator_->AddInterpolator(animation);
479 scrollEndAnimator_->SetDuration(STOP_DURATION);
480 scrollEndAnimator_->Play();
481 }
482 } // namespace OHOS::Ace::NG