1 /*
2  * Copyright (c) 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_overlay_modifier.h"
17 
18 #include "base/geometry/ng/offset_t.h"
19 #include "base/utils/utils.h"
20 #include "core/components_ng/base/modifier.h"
21 #include "core/components_ng/render/drawing.h"
22 #include "core/components_ng/render/drawing_prop_convertor.h"
23 
24 namespace OHOS::Ace::NG {
25 namespace {
26 constexpr double FULL_ALPHA = 255.0;
27 constexpr float HALF = 0.5f;
28 constexpr float SPRING_MOTION_RESPONSE = 0.314f;
29 constexpr float SPRING_MOTION_DAMPING_FRACTION = 0.95f;
30 constexpr int32_t BAR_DISAPPEAR_DURATION = 300;  // 300ms
31 constexpr int32_t BAR_APPEAR_DURATION = 100;     // 100ms
32 constexpr int32_t BAR_GROW_DURATION = 150;       // 150ms, scroll bar width expands from 4dp to 8dp
33 constexpr int32_t BAR_SHRINK_DURATION = 250;     // 250ms, scroll bar width shrinks from 8dp to 4dp
34 constexpr int32_t BAR_DISAPPEAR_FRAME_RATE = 15; // 15fps, the expected frame rate of opacity animation
35 constexpr int32_t BAR_DISAPPEAR_MIN_FRAME_RATE = 0;
36 constexpr int32_t BAR_DISAPPEAR_MAX_FRAME_RATE = 90;
37 constexpr float ADAPT_ACCURACY = 0.5f;
38 } // namespace
39 
ScrollBarOverlayModifier(const OffsetF & barOffset,const SizeF & barSize)40 ScrollBarOverlayModifier::ScrollBarOverlayModifier(const OffsetF& barOffset, const SizeF& barSize)
41 {
42     barX_ = AceType::MakeRefPtr<AnimatablePropertyFloat>(barOffset.GetX());
43     AttachProperty(barX_);
44     barY_ = AceType::MakeRefPtr<AnimatablePropertyFloat>(barOffset.GetY());
45     AttachProperty(barY_);
46     barWidth_ = AceType::MakeRefPtr<AnimatablePropertyFloat>(barSize.Width());
47     AttachProperty(barWidth_);
48     barHeight_ = AceType::MakeRefPtr<AnimatablePropertyFloat>(barSize.Height());
49     AttachProperty(barHeight_);
50     opacity_ = AceType::MakeRefPtr<AnimatablePropertyUint8>(UINT8_MAX);
51     AttachProperty(opacity_);
52     barColor_ = AceType::MakeRefPtr<PropertyColor>(Color());
53     AttachProperty(barColor_);
54 }
55 
onDraw(DrawingContext & drawingContext)56 void ScrollBarOverlayModifier::onDraw(DrawingContext& drawingContext)
57 {
58     CHECK_NULL_VOID(opacity_);
59     CHECK_NULL_VOID(barColor_);
60     CHECK_NULL_VOID(barWidth_);
61     CHECK_NULL_VOID(barHeight_);
62     CHECK_NULL_VOID(barX_);
63     CHECK_NULL_VOID(barY_);
64     CheckMainModeNearEqual();
65     auto barWidth = barWidth_->Get();
66     auto barHeight = barHeight_->Get();
67     auto barX = barX_->Get();
68     auto barY = barY_->Get();
69     if (!NearZero(barWidth) && !NearZero(barHeight)) {
70         auto& canvas = drawingContext.canvas;
71         RSBrush brush;
72         brush.SetBlendMode(RSBlendMode::SRC_OVER);
73         brush.SetAntiAlias(true);
74         RSRect fgRect(barX, barY, barX + barWidth, barY + barHeight);
75         double filletRadius = barWidth * HALF;
76         RSColor barColor = ToRSColor(barColor_->Get().BlendOpacity(opacity_->Get() / FULL_ALPHA));
77         brush.SetColor(barColor);
78         canvas.AttachBrush(brush);
79         canvas.DrawRoundRect({ fgRect, filletRadius, filletRadius });
80         canvas.DetachBrush();
81     }
82 }
83 
SetOffset(const OffsetF & barOffset)84 void ScrollBarOverlayModifier::SetOffset(const OffsetF& barOffset)
85 {
86     CHECK_NULL_VOID(barX_);
87     CHECK_NULL_VOID(barY_);
88     barX_->Set(barOffset.GetX());
89     barY_->Set(barOffset.GetY());
90 }
91 
SetSize(const SizeF & barSize)92 void ScrollBarOverlayModifier::SetSize(const SizeF& barSize)
93 {
94     CHECK_NULL_VOID(barWidth_);
95     CHECK_NULL_VOID(barHeight_);
96     barWidth_->Set(barSize.Width());
97     barHeight_->Set(barSize.Height());
98 }
99 
SetRect(const Rect & fgRect)100 void ScrollBarOverlayModifier::SetRect(const Rect& fgRect)
101 {
102     SetOffset(OffsetF(fgRect.Left(), fgRect.Top()));
103     SetSize(SizeF(fgRect.Width(), fgRect.Height()));
104 }
105 
SetMainModeSize(const Size & size)106 void ScrollBarOverlayModifier::SetMainModeSize(const Size& size)
107 {
108     if (positionMode_ == PositionMode::BOTTOM) {
109         CHECK_NULL_VOID(barWidth_);
110         barWidth_->Set(size.Width());
111     } else {
112         CHECK_NULL_VOID(barHeight_);
113         barHeight_->Set(size.Height());
114     }
115 }
116 
SetCrossModeSize(const Size & size)117 void ScrollBarOverlayModifier::SetCrossModeSize(const Size& size)
118 {
119     if (positionMode_ == PositionMode::BOTTOM) {
120         CHECK_NULL_VOID(barHeight_);
121         barHeight_->Set(size.Height());
122     } else {
123         CHECK_NULL_VOID(barWidth_);
124         barWidth_->Set(size.Width());
125     }
126 }
127 
SetMainModeOffset(const Offset & offset)128 void ScrollBarOverlayModifier::SetMainModeOffset(const Offset& offset)
129 {
130     if (positionMode_ == PositionMode::BOTTOM) {
131         CHECK_NULL_VOID(barX_);
132         barX_->Set(offset.GetX());
133     } else {
134         CHECK_NULL_VOID(barY_);
135         barY_->Set(offset.GetY());
136     }
137 }
138 
SetCrossModeOffset(const Offset & offset)139 void ScrollBarOverlayModifier::SetCrossModeOffset(const Offset& offset)
140 {
141     if (positionMode_ == PositionMode::BOTTOM) {
142         CHECK_NULL_VOID(barY_);
143         barY_->Set(offset.GetY());
144     } else {
145         CHECK_NULL_VOID(barX_);
146         barX_->Set(offset.GetX());
147     }
148 }
149 
StartBarAnimation(HoverAnimationType hoverAnimationType,OpacityAnimationType opacityAnimationType,bool needAdaptAnimation,const Rect & fgRect)150 void ScrollBarOverlayModifier::StartBarAnimation(HoverAnimationType hoverAnimationType,
151     OpacityAnimationType opacityAnimationType, bool needAdaptAnimation, const Rect& fgRect)
152 {
153     CHECK_NULL_VOID(barX_);
154     CHECK_NULL_VOID(barY_);
155     CHECK_NULL_VOID(barWidth_);
156     CHECK_NULL_VOID(barHeight_);
157     if (hoverAnimationType == HoverAnimationType::NONE && !needAdaptAnimation) {
158         SetRect(fgRect);
159     } else {
160         StartHoverAnimation(fgRect, hoverAnimationType);
161         StartAdaptAnimation(fgRect, needAdaptAnimation);
162     }
163     if (opacityAnimationType != OpacityAnimationType::NONE && isScrollable_) {
164         StartOpacityAnimation(opacityAnimationType);
165     }
166 }
167 
StartAdaptAnimation(const Rect & fgRect,bool needAdaptAnimation)168 void ScrollBarOverlayModifier::StartAdaptAnimation(const Rect& fgRect, bool needAdaptAnimation)
169 {
170     CHECK_NULL_VOID(needAdaptAnimation);
171     AnimationOption option;
172     auto motion = AceType::MakeRefPtr<ResponsiveSpringMotion>(SPRING_MOTION_RESPONSE, SPRING_MOTION_DAMPING_FRACTION);
173     option.SetCurve(motion);
174     isAdaptAnimationStop_ = false;
175     adaptAnimation_ = AnimationUtils::StartAnimation(option, [&]() {
176         SetMainModeSize(fgRect.GetSize());
177         SetMainModeOffset(fgRect.GetOffset());
178     });
179 }
180 
StopAdaptAnimation()181 void ScrollBarOverlayModifier::StopAdaptAnimation()
182 {
183     if (adaptAnimation_) {
184         isAdaptAnimationStop_ = true;
185         AnimationUtils::StopAnimation(adaptAnimation_);
186     }
187 }
188 
StartHoverAnimation(const Rect & fgRect,HoverAnimationType hoverAnimationType)189 void ScrollBarOverlayModifier::StartHoverAnimation(const Rect& fgRect, HoverAnimationType hoverAnimationType)
190 {
191     // Only the cross axis offset is updated, the main axis offset will be updated by adaptAnimation.
192     if (hoverAnimationType == HoverAnimationType::NONE) {
193         SetCrossModeSize(fgRect.GetSize());
194         SetCrossModeOffset(fgRect.GetOffset());
195         return;
196     }
197     if (hoverAnimationType != hoverAnimatingType_) {
198         StopHoverAnimation();
199     }
200     // In order to only play the offset of the hover part in the animation, update the cross axis offset in advance
201     SetCrossModeOffset(fgRect.GetOffset() + GetHoverOffset(fgRect.GetSize()));
202     hoverAnimatingType_ = hoverAnimationType;
203     AnimationOption option;
204     option.SetCurve(Curves::SHARP);
205     if (hoverAnimatingType_ == HoverAnimationType::GROW) {
206         option.SetDuration(BAR_GROW_DURATION);
207     } else if (hoverAnimatingType_ == HoverAnimationType::SHRINK) {
208         option.SetDuration(BAR_SHRINK_DURATION);
209     }
210     hoverAnimation_ = AnimationUtils::StartAnimation(
211         option,
212         [&]() {
213             SetCrossModeSize(fgRect.GetSize());
214             SetCrossModeOffset(fgRect.GetOffset());
215         },
216         [weak = WeakClaim(this)]() {
217             auto modifier = weak.Upgrade();
218             CHECK_NULL_VOID(modifier);
219             modifier->SetHoverAnimatingType(HoverAnimationType::NONE);
220         });
221 }
222 
StopHoverAnimation()223 void ScrollBarOverlayModifier::StopHoverAnimation()
224 {
225     if (hoverAnimation_) {
226         AnimationUtils::StopAnimation(hoverAnimation_);
227     }
228 }
229 
StopOpacityAnimation()230 void ScrollBarOverlayModifier::StopOpacityAnimation()
231 {
232     if (opacityAnimation_) {
233         AnimationUtils::StopAnimation(opacityAnimation_);
234     }
235 }
236 
StartOpacityAnimation(OpacityAnimationType opacityAnimationType)237 void ScrollBarOverlayModifier::StartOpacityAnimation(OpacityAnimationType opacityAnimationType)
238 {
239     CHECK_NULL_VOID(opacity_);
240     if (opacityAnimationType != opacityAnimatingType_) {
241         StopOpacityAnimation();
242     } else {
243         return;
244     }
245     AnimationOption option;
246     option.SetCurve(Curves::SHARP);
247     if (opacityAnimationType == OpacityAnimationType::DISAPPEAR) {
248         option.SetFrameRateRange(AceType::MakeRefPtr<FrameRateRange>(
249             BAR_DISAPPEAR_MIN_FRAME_RATE, BAR_DISAPPEAR_MAX_FRAME_RATE, BAR_DISAPPEAR_FRAME_RATE));
250         option.SetDuration(BAR_DISAPPEAR_DURATION);
251     } else if (opacityAnimationType == OpacityAnimationType::APPEAR) {
252         option.SetDuration(BAR_APPEAR_DURATION);
253     }
254     opacityAnimatingType_ = opacityAnimationType;
255     opacityAnimation_ = AnimationUtils::StartAnimation(
256         option,
257         [&]() {
258             if (opacityAnimatingType_ == OpacityAnimationType::DISAPPEAR) {
259                 opacity_->Set(0);
260             } else if (opacityAnimatingType_ == OpacityAnimationType::APPEAR) {
261                 opacity_->Set(UINT8_MAX);
262             }
263         },
264         [weak = WeakClaim(this)]() {
265             auto modifier = weak.Upgrade();
266             CHECK_NULL_VOID(modifier);
267             modifier->SetOpacityAnimatingType(OpacityAnimationType::NONE);
268         });
269 }
270 
SetBarColor(Color barColor)271 void ScrollBarOverlayModifier::SetBarColor(Color barColor)
272 {
273     CHECK_NULL_VOID(barColor_);
274     barColor_->Set(barColor);
275 }
276 
GetHoverOffset(const Size & size) const277 Offset ScrollBarOverlayModifier::GetHoverOffset(const Size& size) const
278 {
279     if (positionMode_ == PositionMode::RIGHT) {
280         return Offset(size.Width() - barWidth_->Get(), 0.f);
281     } else if (positionMode_ == PositionMode::BOTTOM) {
282         return Offset(0.f, size.Height() - barHeight_->Get());
283     }
284     return Offset::Zero();
285 }
286 
CheckMainModeNearEqual()287 void ScrollBarOverlayModifier::CheckMainModeNearEqual()
288 {
289     if (isAdaptAnimationStop_) {
290         return;
291     }
292     float MainModeHeight = 0.f;
293     float MainModeOffset = 0.f;
294     if (positionMode_ == PositionMode::BOTTOM) {
295         MainModeHeight = barWidth_->Get();
296         MainModeOffset = barX_->Get();
297     } else {
298         MainModeHeight = barHeight_->Get();
299         MainModeOffset = barY_->Get();
300     }
301     if (NearEqual(lastMainModeHeight_, MainModeHeight, ADAPT_ACCURACY) &&
302         NearEqual(lastMainModeOffset_, MainModeOffset, ADAPT_ACCURACY)) {
303         StopAdaptAnimation();
304     }
305     lastMainModeHeight_ = MainModeHeight;
306     lastMainModeOffset_ = MainModeOffset;
307 }
308 } // namespace OHOS::Ace::NG
309