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 "bridge/declarative_frontend/jsview/js_scroller.h"
17
18 #include "base/geometry/axis.h"
19 #include "base/utils/linear_map.h"
20 #include "base/utils/utils.h"
21 #include "bridge/declarative_frontend/engine/js_types.h"
22 #include "bridge/declarative_frontend/jsview/js_view_common_def.h"
23 #include "core/animation/curves.h"
24 #include "core/common/container.h"
25 #include "core/components/common/layout/align_declaration.h"
26
27 namespace OHOS::Ace::Framework {
28 namespace {
29
30 constexpr Axis DIRECTION_TABLE[] = { Axis::VERTICAL, Axis::HORIZONTAL };
31
32 constexpr AlignDeclaration::Edge EDGE_TABLE[] = {
33 AlignDeclaration::Edge::TOP,
34 AlignDeclaration::Edge::CENTER,
35 AlignDeclaration::Edge::BOTTOM,
36 AlignDeclaration::Edge::BASELINE,
37 AlignDeclaration::Edge::START,
38 AlignDeclaration::Edge::MIDDLE,
39 AlignDeclaration::Edge::END,
40 };
41
42 // corresponding to EDGE_TABLE[]
43 constexpr ScrollEdgeType EDGE_TYPE_TABLE[] = { ScrollEdgeType::SCROLL_TOP, ScrollEdgeType::SCROLL_NONE,
44 ScrollEdgeType::SCROLL_BOTTOM, ScrollEdgeType::SCROLL_NONE, ScrollEdgeType::SCROLL_TOP, ScrollEdgeType::SCROLL_NONE,
45 ScrollEdgeType::SCROLL_BOTTOM };
46
47 const LinearMapNode<RefPtr<Curve>> CURVE_MAP[] = {
48 { "ease", Curves::EASE },
49 { "ease-in", Curves::EASE_IN },
50 { "ease-in-out", Curves::EASE_IN_OUT },
51 { "ease-out", Curves::EASE_OUT },
52 { "friction", Curves::FRICTION },
53 { "linear", Curves::LINEAR },
54 };
55
56 constexpr double DEFAULT_DURATION = 1000.0;
57 constexpr ScrollAlign ALIGN_TABLE[] = {
58 ScrollAlign::START,
59 ScrollAlign::CENTER,
60 ScrollAlign::END,
61 ScrollAlign::AUTO,
62 };
63
64 const std::regex DIMENSION_REGEX(R"(^[-+]?\d+(?:\.\d+)?(?:px|vp|fp|lpx)?$)", std::regex::icase);
65
66 } // namespace
67
JSBind(BindingTarget globalObj)68 void JSScroller::JSBind(BindingTarget globalObj)
69 {
70 JSClass<JSScroller>::Declare("Scroller");
71 JSClass<JSScroller>::CustomMethod("scrollTo", &JSScroller::ScrollTo);
72 JSClass<JSScroller>::CustomMethod("scrollEdge", &JSScroller::ScrollEdge);
73 JSClass<JSScroller>::CustomMethod("scrollPage", &JSScroller::ScrollPage);
74 JSClass<JSScroller>::CustomMethod("currentOffset", &JSScroller::CurrentOffset);
75 JSClass<JSScroller>::CustomMethod("scrollToIndex", &JSScroller::ScrollToIndex);
76 JSClass<JSScroller>::CustomMethod("scrollBy", &JSScroller::ScrollBy);
77 JSClass<JSScroller>::CustomMethod("isAtEnd", &JSScroller::IsAtEnd);
78 JSClass<JSScroller>::Bind(globalObj, JSScroller::Constructor, JSScroller::Destructor);
79 }
80
Constructor(const JSCallbackInfo & args)81 void JSScroller::Constructor(const JSCallbackInfo& args)
82 {
83 auto scroller = Referenced::MakeRefPtr<JSScroller>();
84 scroller->IncRefCount();
85 args.SetReturnValue(Referenced::RawPtr(scroller));
86 }
87
Destructor(JSScroller * scroller)88 void JSScroller::Destructor(JSScroller* scroller)
89 {
90 if (scroller != nullptr) {
91 scroller->DecRefCount();
92 }
93 }
94
ScrollTo(const JSCallbackInfo & args)95 void JSScroller::ScrollTo(const JSCallbackInfo& args)
96 {
97 if (args.Length() < 1 || !args[0]->IsObject()) {
98 LOGW("Invalid params");
99 return;
100 }
101
102 JSRef<JSObject> obj = JSRef<JSObject>::Cast(args[0]);
103 Dimension xOffset;
104 Dimension yOffset;
105 auto xOffsetStr = obj->GetProperty("xOffset");
106 auto yOffsetStr = obj->GetProperty("yOffset");
107 if (!std::regex_match(xOffsetStr->ToString(), DIMENSION_REGEX) ||
108 !std::regex_match(yOffsetStr->ToString(), DIMENSION_REGEX) || !ConvertFromJSValue(xOffsetStr, xOffset) ||
109 !ConvertFromJSValue(yOffsetStr, yOffset)) {
110 LOGW("Failed to parse param 'xOffset' or 'yOffset'");
111 return;
112 }
113
114 double duration = 0.0;
115 bool smooth = false;
116 RefPtr<Curve> curve = Curves::EASE;
117 auto animationValue = obj->GetProperty("animation");
118 if (animationValue->IsObject()) {
119 auto animationObj = JSRef<JSObject>::Cast(animationValue);
120 if (!ConvertFromJSValue(animationObj->GetProperty("duration"), duration) || NonPositive(duration)) {
121 LOGW("Failed to parse param 'duration' or it is not a positive number, set it as the default value");
122 duration = DEFAULT_DURATION;
123 }
124
125 auto curveArgs = animationObj->GetProperty("curve");
126 ParseCurveParams(curve, curveArgs);
127 } else if (animationValue->IsBoolean()) {
128 smooth = animationValue->ToBoolean();
129 }
130
131 if (GreatNotEqual(duration, 0.0)) {
132 LOGD("ScrollTo(%lf, %lf, %lf)", xOffset.Value(), yOffset.Value(), duration);
133 } else {
134 LOGD("ScrollTo(%lf, %lf)", xOffset.Value(), yOffset.Value());
135 }
136 auto scrollController = controllerWeak_.Upgrade();
137 if (!scrollController) {
138 LOGE("controller_ is nullptr");
139 return;
140 }
141 auto direction = scrollController->GetScrollDirection();
142 auto position = direction == Axis::VERTICAL ? yOffset : xOffset;
143 scrollController->AnimateTo(position, static_cast<float>(duration), curve, smooth);
144 }
145
ParseCurveParams(RefPtr<Curve> & curve,const JSRef<JSVal> & jsValue)146 void JSScroller::ParseCurveParams(RefPtr<Curve>& curve, const JSRef<JSVal>& jsValue)
147 {
148 std::string curveName;
149 if (ConvertFromJSValue(jsValue, curveName)) {
150 auto index = BinarySearchFindIndex(CURVE_MAP, ArraySize(CURVE_MAP), curveName.c_str());
151 if (index >= 0) {
152 curve = CURVE_MAP[index].value;
153 }
154 } else if (jsValue->IsObject()) {
155 auto icurveArgs = JsonUtil::ParseJsonString(jsValue->ToString());
156 if (icurveArgs->IsObject()) {
157 auto curveString = icurveArgs->GetValue("__curveString");
158 curve = CreateCurve(curveString->GetString());
159 }
160 }
161 }
162
ScrollEdge(const JSCallbackInfo & args)163 void JSScroller::ScrollEdge(const JSCallbackInfo& args)
164 {
165 AlignDeclaration::Edge edge = AlignDeclaration::Edge::AUTO;
166 if (args.Length() < 1 || !ConvertFromJSValue(args[0], EDGE_TABLE, edge)) {
167 LOGW("Invalid params");
168 return;
169 }
170 auto scrollController = controllerWeak_.Upgrade();
171 if (!scrollController) {
172 LOGE("controller_ is nullptr");
173 return;
174 }
175 LOGD("ScrollEdge(%{public}d)", static_cast<int32_t>(edge));
176 ScrollEdgeType edgeType = EDGE_TYPE_TABLE[static_cast<int32_t>(edge)];
177 scrollController->ScrollToEdge(edgeType, true);
178 }
179
ScrollToIndex(const JSCallbackInfo & args)180 void JSScroller::ScrollToIndex(const JSCallbackInfo& args)
181 {
182 int32_t index = 0;
183 bool smooth = false;
184 ScrollAlign align = ScrollAlign::NONE;
185 if (args.Length() < 1 || !ConvertFromJSValue(args[0], index) || index < 0) {
186 LOGW("Invalid params");
187 return;
188 }
189 auto scrollController = controllerWeak_.Upgrade();
190 if (!scrollController) {
191 LOGE("controller_ is nullptr");
192 return;
193 }
194 // 2:parameters count, 1: parameter index
195 if (args.Length() >= 2 && args[1]->IsBoolean()) {
196 smooth = args[1]->ToBoolean();
197 }
198 // 3:parameters count, 2: parameter index
199 if (args.Length() == 3 && !ConvertFromJSValue(args[2], ALIGN_TABLE, align)) {
200 LOGE("Invalid align params");
201 }
202 scrollController->JumpTo(index, smooth, align, SCROLL_FROM_JUMP);
203 }
204
ScrollPage(const JSCallbackInfo & args)205 void JSScroller::ScrollPage(const JSCallbackInfo& args)
206 {
207 if (args.Length() < 1 || !args[0]->IsObject()) {
208 LOGW("Invalid params");
209 return;
210 }
211
212 auto obj = JSRef<JSObject>::Cast(args[0]);
213 bool next = true;
214 if (!ConvertFromJSValue(obj->GetProperty("next"), next)) {
215 LOGW("Failed to parse param 'next'");
216 return;
217 }
218
219 Axis direction = Axis::NONE;
220 ConvertFromJSValue(obj->GetProperty("direction"), DIRECTION_TABLE, direction);
221 auto scrollController = controllerWeak_.Upgrade();
222 if (!scrollController) {
223 LOGE("controller_ is nullptr");
224 return;
225 }
226 LOGD("ScrollPage(%{public}s, %{public}d)", next ? "true" : "false", static_cast<int32_t>(direction));
227 scrollController->ScrollPage(!next, true);
228 }
229
CurrentOffset(const JSCallbackInfo & args)230 void JSScroller::CurrentOffset(const JSCallbackInfo& args)
231 {
232 LOGD("CurrentOffset()");
233 auto scrollController = controllerWeak_.Upgrade();
234 if (!scrollController) {
235 LOGE("controller_ is nullptr");
236 return;
237 }
238 auto retObj = JSRef<JSObject>::New();
239 auto offset = scrollController->GetCurrentOffset();
240 retObj->SetProperty("xOffset", offset.GetX());
241 retObj->SetProperty("yOffset", offset.GetY());
242 args.SetReturnValue(retObj);
243 }
244
ScrollBy(const JSCallbackInfo & args)245 void JSScroller::ScrollBy(const JSCallbackInfo& args)
246 {
247 if (args.Length() < 2) {
248 LOGW("Invalid params");
249 return;
250 }
251
252 Dimension xOffset;
253 Dimension yOffset;
254 if (!ConvertFromJSValue(args[0], xOffset) ||
255 !ConvertFromJSValue(args[1], yOffset)) {
256 LOGW("Failed to parse param");
257 return;
258 }
259
260 auto deltaX = xOffset.Value();
261 auto deltaY = yOffset.Value();
262 auto container = Container::Current();
263 if (container) {
264 auto context = container->GetPipelineContext();
265 if (context) {
266 if (xOffset.Unit() == DimensionUnit::PERCENT) {
267 deltaX = 0.0;
268 } else {
269 deltaX = context->NormalizeToPx(xOffset);
270 }
271 if (yOffset.Unit() == DimensionUnit::PERCENT) {
272 deltaY = 0.0;
273 } else {
274 deltaY = context->NormalizeToPx(yOffset);
275 }
276 }
277 }
278 auto scrollController = controllerWeak_.Upgrade();
279 if (scrollController) {
280 scrollController->ScrollBy(deltaX, deltaY, false);
281 }
282 }
283
IsAtEnd(const JSCallbackInfo & args)284 void JSScroller::IsAtEnd(const JSCallbackInfo& args)
285 {
286 auto scrollController = controllerWeak_.Upgrade();
287 if (!scrollController) {
288 LOGE("controller_ is nullptr");
289 return;
290 }
291 bool isAtEnd = scrollController->IsAtEnd();
292 auto retVal = JSRef<JSVal>::Make(ToJSValue(isAtEnd));
293 args.SetReturnValue(retVal);
294 }
295 } // namespace OHOS::Ace::Framework
296