1 /*
2 * Copyright (c) 2021-2024 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 } // namespace
66
JSBind(BindingTarget globalObj)67 void JSScroller::JSBind(BindingTarget globalObj)
68 {
69 JSClass<JSScroller>::Declare("Scroller");
70 JSClass<JSScroller>::CustomMethod("scrollTo", &JSScroller::ScrollTo);
71 JSClass<JSScroller>::CustomMethod("scrollEdge", &JSScroller::ScrollEdge);
72 JSClass<JSScroller>::CustomMethod("fling", &JSScroller::Fling);
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>::CustomMethod("getItemRect", &JSScroller::GetItemRect);
79 JSClass<JSScroller>::Bind(globalObj, JSScroller::Constructor, JSScroller::Destructor);
80 }
81
Constructor(const JSCallbackInfo & args)82 void JSScroller::Constructor(const JSCallbackInfo& args)
83 {
84 auto scroller = Referenced::MakeRefPtr<JSScroller>();
85 scroller->IncRefCount();
86 args.SetReturnValue(Referenced::RawPtr(scroller));
87 }
88
Destructor(JSScroller * scroller)89 void JSScroller::Destructor(JSScroller* scroller)
90 {
91 if (scroller != nullptr) {
92 scroller->DecRefCount();
93 }
94 }
95
CreateRectangle(const Rect & info)96 JSRef<JSObject> JSScroller::CreateRectangle(const Rect& info)
97 {
98 JSRef<JSObject> rectObj = JSRef<JSObject>::New();
99 rectObj->SetProperty<double>("x", info.Left());
100 rectObj->SetProperty<double>("y", info.Top());
101 rectObj->SetProperty<double>("width", info.Width());
102 rectObj->SetProperty<double>("height", info.Height());
103 return rectObj;
104 }
105
ScrollTo(const JSCallbackInfo & args)106 void JSScroller::ScrollTo(const JSCallbackInfo& args)
107 {
108 if (args.Length() < 1 || !args[0]->IsObject()) {
109 return;
110 }
111
112 JSRef<JSObject> obj = JSRef<JSObject>::Cast(args[0]);
113 Dimension xOffset;
114 Dimension yOffset;
115 auto xOffsetStr = obj->GetProperty("xOffset");
116 auto yOffsetStr = obj->GetProperty("yOffset");
117 if (!std::regex_match(xOffsetStr->ToString(), DIMENSION_REGEX) ||
118 !std::regex_match(yOffsetStr->ToString(), DIMENSION_REGEX) || !ConvertFromJSValue(xOffsetStr, xOffset) ||
119 !ConvertFromJSValue(yOffsetStr, yOffset)) {
120 return;
121 }
122
123 double duration = 0.0;
124 bool smooth = false;
125 bool canOverScroll = false;
126 RefPtr<Curve> curve = Curves::EASE;
127 auto animationValue = obj->GetProperty("animation");
128 if (animationValue->IsObject()) {
129 auto animationObj = JSRef<JSObject>::Cast(animationValue);
130 auto curveArgs = animationObj->GetProperty("curve");
131 bool hasDuration = true;
132 if (!ConvertFromJSValue(animationObj->GetProperty("duration"), duration) || Negative(duration)) {
133 duration = DEFAULT_DURATION;
134 hasDuration = false;
135 }
136 bool hasCurve = ParseCurveParams(curve, curveArgs);
137 bool hasCanOverScroll =
138 ConvertFromJSValue(animationObj->GetProperty("canOverScroll"), canOverScroll) ? true : false;
139 smooth = !hasDuration && !hasCurve && !hasCanOverScroll ? true : false;
140 } else if (animationValue->IsBoolean()) {
141 smooth = animationValue->ToBoolean();
142 }
143 auto scrollController = controllerWeak_.Upgrade();
144 if (!scrollController) {
145 return;
146 }
147 ContainerScope scope(instanceId_);
148 auto direction = scrollController->GetScrollDirection();
149 auto position = direction == Axis::VERTICAL ? yOffset : xOffset;
150 scrollController->AnimateTo(position, static_cast<float>(duration), curve, smooth, canOverScroll);
151 }
152
ParseCurveParams(RefPtr<Curve> & curve,const JSRef<JSVal> & jsValue)153 bool JSScroller::ParseCurveParams(RefPtr<Curve>& curve, const JSRef<JSVal>& jsValue)
154 {
155 std::string curveName;
156 if (ConvertFromJSValue(jsValue, curveName)) {
157 auto index = BinarySearchFindIndex(CURVE_MAP, ArraySize(CURVE_MAP), curveName.c_str());
158 if (index >= 0) {
159 curve = CURVE_MAP[index].value;
160 return true;
161 }
162 } else if (jsValue->IsObject()) {
163 JSRef<JSVal> curveString = JSRef<JSObject>::Cast(jsValue)->GetProperty("__curveString");
164 if (curveString->IsString()) {
165 curve = CreateCurve(curveString->ToString());
166 return true;
167 }
168 }
169 return false;
170 }
171
ScrollEdge(const JSCallbackInfo & args)172 void JSScroller::ScrollEdge(const JSCallbackInfo& args)
173 {
174 AlignDeclaration::Edge edge = AlignDeclaration::Edge::AUTO;
175 if (args.Length() < 1 || !ConvertFromJSValue(args[0], EDGE_TABLE, edge)) {
176 return;
177 }
178 auto scrollController = controllerWeak_.Upgrade();
179 if (!scrollController) {
180 return;
181 }
182 ScrollEdgeType edgeType = EDGE_TYPE_TABLE[static_cast<int32_t>(edge)];
183 ContainerScope scope(instanceId_);
184
185 if (args.Length() > 1 && args[1]->IsObject()) {
186 auto obj = JSRef<JSObject>::Cast(args[1]);
187 float velocity = 0.0f;
188 if (ConvertFromJSValue(obj->GetProperty("velocity"), velocity)) {
189 if (velocity > 0) {
190 velocity = Dimension(velocity, DimensionUnit::VP).ConvertToPx();
191 scrollController->ScrollToEdge(edgeType, velocity);
192 return;
193 }
194 }
195 }
196 scrollController->ScrollToEdge(edgeType, true);
197 }
198
Fling(const JSCallbackInfo & args)199 void JSScroller::Fling(const JSCallbackInfo& args)
200 {
201 auto scrollController = controllerWeak_.Upgrade();
202 if (!scrollController) {
203 JSException::Throw(ERROR_CODE_NAMED_ROUTE_ERROR, "%s", "Controller not bound to component.");
204 return;
205 }
206 double flingVelocity = 0.0;
207 if (!args[0]->IsNumber()) {
208 JSException::Throw(ERROR_CODE_PARAM_INVALID, "%s", "The parameter check failed.");
209 return;
210 }
211 flingVelocity = args[0]->ToNumber<double>();
212 if (NearZero(flingVelocity)) {
213 return;
214 }
215 ContainerScope scope(instanceId_);
216 flingVelocity = Dimension(flingVelocity, DimensionUnit::VP).ConvertToPx();
217 scrollController->Fling(flingVelocity);
218 }
219
ScrollToIndex(const JSCallbackInfo & args)220 void JSScroller::ScrollToIndex(const JSCallbackInfo& args)
221 {
222 int32_t index = 0;
223 bool smooth = false;
224 ScrollAlign align = ScrollAlign::NONE;
225 if (args.Length() < 1 || !ConvertFromJSValue(args[0], index) || index < 0) {
226 return;
227 }
228 auto scrollController = controllerWeak_.Upgrade();
229 if (!scrollController) {
230 return;
231 }
232 // 2: parameters count, 1: parameter index
233 auto smoothArg = args[1];
234 if (args.Length() >= 2 && smoothArg->IsBoolean()) {
235 smooth = smoothArg->ToBoolean();
236 }
237 // 3: parameters count, 2: parameter index
238 if (args.Length() >= 3) {
239 ConvertFromJSValue(args[2], ALIGN_TABLE, align);
240 }
241
242 // 4: parameters count, 3: parameter index
243 std::optional<float> extraOffset = std::nullopt;
244 auto optionArg = args[3];
245 if (args.Length() == 4 && optionArg->IsObject()) {
246 auto obj = JSRef<JSObject>::Cast(optionArg);
247 CalcDimension offset;
248 if (JSViewAbstract::ParseLengthMetricsToDimension(obj->GetProperty("extraOffset"), offset)) {
249 auto offsetPx = offset.ConvertToPx();
250 if (!std::isnan(offsetPx)) {
251 extraOffset = offsetPx;
252 }
253 }
254 }
255 ContainerScope scope(instanceId_);
256 scrollController->ScrollToIndex(index, smooth, align, extraOffset);
257 }
258
ScrollPage(const JSCallbackInfo & args)259 void JSScroller::ScrollPage(const JSCallbackInfo& args)
260 {
261 if (args.Length() < 1 || !args[0]->IsObject()) {
262 return;
263 }
264
265 auto obj = JSRef<JSObject>::Cast(args[0]);
266 bool next = true;
267 if (!ConvertFromJSValue(obj->GetProperty("next"), next)) {
268 return;
269 }
270 Axis direction = Axis::NONE;
271 ConvertFromJSValue(obj->GetProperty("direction"), DIRECTION_TABLE, direction);
272 auto scrollController = controllerWeak_.Upgrade();
273 if (!scrollController) {
274 return;
275 }
276 ContainerScope scope(instanceId_);
277 scrollController->ScrollPage(!next, true);
278 }
279
CurrentOffset(const JSCallbackInfo & args)280 void JSScroller::CurrentOffset(const JSCallbackInfo& args)
281 {
282 auto scrollController = controllerWeak_.Upgrade();
283 if (!scrollController) {
284 return;
285 }
286 auto retObj = JSRef<JSObject>::New();
287 ContainerScope scope(instanceId_);
288 auto offset = scrollController->GetCurrentOffset();
289 retObj->SetProperty("xOffset", offset.GetX());
290 retObj->SetProperty("yOffset", offset.GetY());
291 args.SetReturnValue(retObj);
292 }
293
ScrollBy(const JSCallbackInfo & args)294 void JSScroller::ScrollBy(const JSCallbackInfo& args)
295 {
296 if (args.Length() < 2) {
297 return;
298 }
299
300 Dimension xOffset;
301 Dimension yOffset;
302 if (!ConvertFromJSValue(args[0], xOffset) ||
303 !ConvertFromJSValue(args[1], yOffset)) {
304 return;
305 }
306 auto scrollController = controllerWeak_.Upgrade();
307 if (!scrollController) {
308 return;
309 }
310
311 ContainerScope scope(instanceId_);
312 auto deltaX = xOffset.Value();
313 auto deltaY = yOffset.Value();
314 auto container = Container::Current();
315 if (container) {
316 auto context = container->GetPipelineContext();
317 if (context) {
318 if (xOffset.Unit() == DimensionUnit::PERCENT) {
319 deltaX = 0.0;
320 } else {
321 deltaX = context->NormalizeToPx(xOffset);
322 }
323 if (yOffset.Unit() == DimensionUnit::PERCENT) {
324 deltaY = 0.0;
325 } else {
326 deltaY = context->NormalizeToPx(yOffset);
327 }
328 }
329 }
330 scrollController->ScrollBy(deltaX, deltaY, false);
331 }
332
IsAtEnd(const JSCallbackInfo & args)333 void JSScroller::IsAtEnd(const JSCallbackInfo& args)
334 {
335 auto scrollController = controllerWeak_.Upgrade();
336 if (!scrollController) {
337 return;
338 }
339 ContainerScope scope(instanceId_);
340 bool isAtEnd = scrollController->IsAtEnd();
341 auto retVal = JSRef<JSVal>::Make(ToJSValue(isAtEnd));
342 args.SetReturnValue(retVal);
343 }
344
GetItemRect(const JSCallbackInfo & args)345 void JSScroller::GetItemRect(const JSCallbackInfo& args)
346 {
347 int32_t index = -1;
348 if (args.Length() != 1 || !ConvertFromJSValue(args[0], index)) {
349 JSException::Throw(ERROR_CODE_PARAM_INVALID, "%s", "Input parameter check failed.");
350 return;
351 }
352 auto scrollController = controllerWeak_.Upgrade();
353 if (scrollController) {
354 ContainerScope scope(instanceId_);
355 auto rectObj = CreateRectangle(scrollController->GetItemRect(index));
356 JSRef<JSVal> rect = JSRef<JSObject>::Cast(rectObj);
357 args.SetReturnValue(rect);
358 } else {
359 JSException::Throw(ERROR_CODE_NAMED_ROUTE_ERROR, "%s", "Controller not bound to component.");
360 }
361 }
362 } // namespace OHOS::Ace::Framework
363