• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/log/event_report.h"
20 #include "base/utils/linear_map.h"
21 #include "base/utils/utils.h"
22 #include "bridge/declarative_frontend/engine/js_types.h"
23 #include "bridge/declarative_frontend/jsview/js_view_common_def.h"
24 #include "core/animation/curves.h"
25 #include "core/common/container.h"
26 #include "core/components/common/layout/align_declaration.h"
27 
28 namespace OHOS::Ace::Framework {
29 namespace {
30 
31 constexpr AlignDeclaration::Edge EDGE_TABLE[] = {
32     AlignDeclaration::Edge::TOP,
33     AlignDeclaration::Edge::CENTER,
34     AlignDeclaration::Edge::BOTTOM,
35     AlignDeclaration::Edge::BASELINE,
36     AlignDeclaration::Edge::START,
37     AlignDeclaration::Edge::MIDDLE,
38     AlignDeclaration::Edge::END,
39 };
40 
41 // corresponding to EDGE_TABLE[]
42 constexpr ScrollEdgeType EDGE_TYPE_TABLE[] = { ScrollEdgeType::SCROLL_TOP, ScrollEdgeType::SCROLL_NONE,
43     ScrollEdgeType::SCROLL_BOTTOM, ScrollEdgeType::SCROLL_NONE, ScrollEdgeType::SCROLL_TOP, ScrollEdgeType::SCROLL_NONE,
44     ScrollEdgeType::SCROLL_BOTTOM };
45 
46 const LinearMapNode<RefPtr<Curve>> CURVE_MAP[] = {
47     { "ease", Curves::EASE },
48     { "ease-in", Curves::EASE_IN },
49     { "ease-in-out", Curves::EASE_IN_OUT },
50     { "ease-out", Curves::EASE_OUT },
51     { "friction", Curves::FRICTION },
52     { "linear", Curves::LINEAR },
53 };
54 
55 static constexpr int ARGS_LENGTH = 2;
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>::CustomMethod("getItemIndex", &JSScroller::GetItemIndex);
80     JSClass<JSScroller>::Bind(globalObj, JSScroller::Constructor, JSScroller::Destructor);
81 }
82 
Constructor(const JSCallbackInfo & args)83 void JSScroller::Constructor(const JSCallbackInfo& args)
84 {
85     auto scroller = Referenced::MakeRefPtr<JSScroller>();
86     scroller->IncRefCount();
87     args.SetReturnValue(Referenced::RawPtr(scroller));
88 }
89 
Destructor(JSScroller * scroller)90 void JSScroller::Destructor(JSScroller* scroller)
91 {
92     if (scroller != nullptr) {
93         scroller->DecRefCount();
94     }
95 }
96 
CreateRectangle(const Rect & info)97 JSRef<JSObject> JSScroller::CreateRectangle(const Rect& info)
98 {
99     JSRef<JSObject> rectObj = JSRef<JSObject>::New();
100     rectObj->SetProperty<double>("x", info.Left());
101     rectObj->SetProperty<double>("y", info.Top());
102     rectObj->SetProperty<double>("width", info.Width());
103     rectObj->SetProperty<double>("height", info.Height());
104     return rectObj;
105 }
106 
ScrollTo(const JSCallbackInfo & args)107 void JSScroller::ScrollTo(const JSCallbackInfo& args)
108 {
109     if (args.Length() < 1 || !args[0]->IsObject()) {
110         return;
111     }
112 
113     JSRef<JSObject> obj = JSRef<JSObject>::Cast(args[0]);
114     Dimension xOffset;
115     Dimension yOffset;
116     auto xOffsetStr = obj->GetProperty("xOffset");
117     auto yOffsetStr = obj->GetProperty("yOffset");
118     if (!std::regex_match(xOffsetStr->ToString(), DIMENSION_REGEX) ||
119         !std::regex_match(yOffsetStr->ToString(), DIMENSION_REGEX) || !ConvertFromJSValue(xOffsetStr, xOffset) ||
120         !ConvertFromJSValue(yOffsetStr, yOffset)) {
121         return;
122     }
123 
124     double duration = 0.0;
125     bool smooth = false;
126     bool canOverScroll = false;
127     RefPtr<Curve> curve = Curves::EASE;
128     auto animationValue = obj->GetProperty("animation");
129     if (animationValue->IsObject()) {
130         auto animationObj = JSRef<JSObject>::Cast(animationValue);
131         auto curveArgs = animationObj->GetProperty("curve");
132         bool hasDuration = true;
133         if (!ConvertFromJSValue(animationObj->GetProperty("duration"), duration) || Negative(duration)) {
134             duration = DEFAULT_DURATION;
135             hasDuration = false;
136         }
137         bool hasCurve = ParseCurveParams(curve, curveArgs);
138         bool hasCanOverScroll =
139             ConvertFromJSValue(animationObj->GetProperty("canOverScroll"), canOverScroll) ? true : false;
140         smooth = !hasDuration && !hasCurve && !hasCanOverScroll ? true : false;
141     } else if (animationValue->IsBoolean()) {
142         smooth = animationValue->ToBoolean();
143     }
144     auto scrollController = controllerWeak_.Upgrade();
145     if (!scrollController) {
146         EventReport::ReportScrollableErrorEvent("Scroller", ScrollableErrorType::CONTROLLER_NOT_BIND,
147             "The controller does not bind a component when calling ScrollTo function");
148         return;
149     }
150     ContainerScope scope(instanceId_);
151     auto direction = scrollController->GetScrollDirection();
152     auto position = direction == Axis::VERTICAL ? yOffset : xOffset;
153     scrollController->AnimateTo(position, static_cast<float>(duration), curve, smooth, canOverScroll);
154 }
155 
ParseCurveParams(RefPtr<Curve> & curve,const JSRef<JSVal> & jsValue)156 bool JSScroller::ParseCurveParams(RefPtr<Curve>& curve, const JSRef<JSVal>& jsValue)
157 {
158     std::string curveName;
159     if (ConvertFromJSValue(jsValue, curveName)) {
160         auto index = BinarySearchFindIndex(CURVE_MAP, ArraySize(CURVE_MAP), curveName.c_str());
161         if (index >= 0) {
162             curve = CURVE_MAP[index].value;
163             return true;
164         }
165     } else if (jsValue->IsObject()) {
166         JSRef<JSVal> curveString = JSRef<JSObject>::Cast(jsValue)->GetProperty("__curveString");
167         if (curveString->IsString()) {
168             curve = CreateCurve(curveString->ToString());
169             return true;
170         }
171     }
172     return false;
173 }
174 
ScrollEdge(const JSCallbackInfo & args)175 void JSScroller::ScrollEdge(const JSCallbackInfo& args)
176 {
177     AlignDeclaration::Edge edge = AlignDeclaration::Edge::AUTO;
178     if (args.Length() < 1 || !ConvertFromJSValue(args[0], EDGE_TABLE, edge)) {
179         return;
180     }
181     auto scrollController = controllerWeak_.Upgrade();
182     if (!scrollController) {
183         EventReport::ReportScrollableErrorEvent("Scroller", ScrollableErrorType::CONTROLLER_NOT_BIND,
184             "The controller does not bind a component when calling ScrollEdge function");
185         return;
186     }
187     ScrollEdgeType edgeType = EDGE_TYPE_TABLE[static_cast<int32_t>(edge)];
188     ContainerScope scope(instanceId_);
189 
190     if (args.Length() > 1 && args[1]->IsObject()) {
191         auto obj = JSRef<JSObject>::Cast(args[1]);
192         float velocity = 0.0f;
193         if (ConvertFromJSValue(obj->GetProperty("velocity"), velocity)) {
194             if (velocity > 0) {
195                 velocity = Dimension(velocity, DimensionUnit::VP).ConvertToPx();
196                 scrollController->ScrollToEdge(edgeType, velocity);
197                 return;
198             }
199         }
200     }
201     scrollController->ScrollToEdge(edgeType, true);
202 }
203 
Fling(const JSCallbackInfo & args)204 void JSScroller::Fling(const JSCallbackInfo& args)
205 {
206     auto scrollController = controllerWeak_.Upgrade();
207     if (!scrollController) {
208         JSException::Throw(ERROR_CODE_NAMED_ROUTE_ERROR, "%s", "Controller not bound to component.");
209         return;
210     }
211     double flingVelocity = 0.0;
212     if (!args[0]->IsNumber()) {
213         JSException::Throw(ERROR_CODE_PARAM_INVALID, "%s", "The parameter check failed.");
214         return;
215     }
216     flingVelocity = args[0]->ToNumber<double>();
217     if (NearZero(flingVelocity)) {
218         return;
219     }
220     ContainerScope scope(instanceId_);
221     flingVelocity = Dimension(flingVelocity, DimensionUnit::VP).ConvertToPx();
222     scrollController->Fling(flingVelocity);
223 }
224 
ScrollToIndex(const JSCallbackInfo & args)225 void JSScroller::ScrollToIndex(const JSCallbackInfo& args)
226 {
227     int32_t index = 0;
228     bool smooth = false;
229     ScrollAlign align = ScrollAlign::NONE;
230     if (args.Length() < 1 || !ConvertFromJSValue(args[0], index) || index < 0) {
231         return;
232     }
233     auto scrollController = controllerWeak_.Upgrade();
234     if (!scrollController) {
235         EventReport::ReportScrollableErrorEvent("Scroller", ScrollableErrorType::CONTROLLER_NOT_BIND,
236             "The controller does not bind a component when calling ScrollToIndex function");
237         return;
238     }
239     // 2: parameters count, 1: parameter index
240     auto smoothArg = args[1];
241     if (args.Length() >= 2 && smoothArg->IsBoolean()) {
242         smooth = smoothArg->ToBoolean();
243     }
244     // 3: parameters count, 2: parameter index
245     if (args.Length() >= 3) {
246         ConvertFromJSValue(args[2], ALIGN_TABLE, align);
247     }
248 
249     // 4: parameters count, 3: parameter index
250     std::optional<float> extraOffset = std::nullopt;
251     auto optionArg = args[3];
252     if (args.Length() == 4 && optionArg->IsObject()) {
253         auto obj = JSRef<JSObject>::Cast(optionArg);
254         CalcDimension offset;
255         if (JSViewAbstract::ParseLengthMetricsToDimension(obj->GetProperty("extraOffset"), offset)) {
256             auto offsetPx = offset.ConvertToPx();
257             if (!std::isnan(offsetPx)) {
258                 extraOffset = offsetPx;
259             }
260         }
261     }
262     ContainerScope scope(instanceId_);
263     scrollController->ScrollToIndex(index, smooth, align, extraOffset);
264 }
265 
ScrollPage(const JSCallbackInfo & args)266 void JSScroller::ScrollPage(const JSCallbackInfo& args)
267 {
268     if (args.Length() < 1 || !args[0]->IsObject()) {
269         return;
270     }
271 
272     auto obj = JSRef<JSObject>::Cast(args[0]);
273     bool next = true;
274     if (!ConvertFromJSValue(obj->GetProperty("next"), next)) {
275         return;
276     }
277     bool smooth = false;
278     auto smoothValue = obj->GetProperty("animation");
279     if (smoothValue->IsBoolean()) {
280         smooth = smoothValue->ToBoolean();
281     }
282     auto scrollController = controllerWeak_.Upgrade();
283     if (!scrollController) {
284         EventReport::ReportScrollableErrorEvent("Scroller", ScrollableErrorType::CONTROLLER_NOT_BIND,
285             "The controller does not bind a component when calling ScrollPage function");
286         return;
287     }
288     ContainerScope scope(instanceId_);
289     scrollController->ScrollPage(!next, smooth);
290 }
291 
CurrentOffset(const JSCallbackInfo & args)292 void JSScroller::CurrentOffset(const JSCallbackInfo& args)
293 {
294     auto scrollController = controllerWeak_.Upgrade();
295     if (!scrollController) {
296         EventReport::ReportScrollableErrorEvent("Scroller", ScrollableErrorType::CONTROLLER_NOT_BIND,
297             "The controller does not bind a component when calling CurrentOffset function");
298         return;
299     }
300     auto retObj = JSRef<JSObject>::New();
301     ContainerScope scope(instanceId_);
302     auto offset = scrollController->GetCurrentOffset();
303     retObj->SetProperty("xOffset", offset.GetX());
304     retObj->SetProperty("yOffset", offset.GetY());
305     args.SetReturnValue(retObj);
306 }
307 
ScrollBy(const JSCallbackInfo & args)308 void JSScroller::ScrollBy(const JSCallbackInfo& args)
309 {
310     if (args.Length() < 2) {
311         return;
312     }
313 
314     Dimension xOffset;
315     Dimension yOffset;
316     if (!ConvertFromJSValue(args[0], xOffset) ||
317         !ConvertFromJSValue(args[1], yOffset)) {
318         return;
319     }
320     auto scrollController = controllerWeak_.Upgrade();
321     if (!scrollController) {
322         EventReport::ReportScrollableErrorEvent("Scroller", ScrollableErrorType::CONTROLLER_NOT_BIND,
323             "The controller does not bind a component when calling ScrollBy function");
324         return;
325     }
326 
327     ContainerScope scope(instanceId_);
328     auto deltaX = xOffset.Value();
329     auto deltaY = yOffset.Value();
330     auto container = Container::Current();
331     if (container) {
332         auto context = container->GetPipelineContext();
333         if (context) {
334             if (xOffset.Unit() == DimensionUnit::PERCENT) {
335                 deltaX = 0.0;
336             } else {
337                 deltaX = context->NormalizeToPx(xOffset);
338             }
339             if (yOffset.Unit() == DimensionUnit::PERCENT) {
340                 deltaY = 0.0;
341             } else {
342                 deltaY = context->NormalizeToPx(yOffset);
343             }
344         }
345     }
346     scrollController->ScrollBy(deltaX, deltaY, false);
347 }
348 
IsAtEnd(const JSCallbackInfo & args)349 void JSScroller::IsAtEnd(const JSCallbackInfo& args)
350 {
351     auto scrollController = controllerWeak_.Upgrade();
352     if (!scrollController) {
353         EventReport::ReportScrollableErrorEvent("Scroller", ScrollableErrorType::CONTROLLER_NOT_BIND,
354             "The controller does not bind a component when calling IsAtEnd function");
355         return;
356     }
357     ContainerScope scope(instanceId_);
358     bool isAtEnd = scrollController->IsAtEnd();
359     auto retVal = JSRef<JSVal>::Make(ToJSValue(isAtEnd));
360     args.SetReturnValue(retVal);
361 }
362 
GetItemRect(const JSCallbackInfo & args)363 void JSScroller::GetItemRect(const JSCallbackInfo& args)
364 {
365     int32_t index = -1;
366     if (args.Length() != 1 || !ConvertFromJSValue(args[0], index)) {
367         JSException::Throw(ERROR_CODE_PARAM_INVALID, "%s", "Input parameter check failed.");
368         return;
369     }
370     auto scrollController = controllerWeak_.Upgrade();
371     if (scrollController) {
372         ContainerScope scope(instanceId_);
373         auto rectObj = CreateRectangle(scrollController->GetItemRect(index));
374         JSRef<JSVal> rect = JSRef<JSObject>::Cast(rectObj);
375         args.SetReturnValue(rect);
376     } else {
377         JSException::Throw(ERROR_CODE_NAMED_ROUTE_ERROR, "%s", "Controller not bound to component.");
378     }
379 }
380 
GetItemIndex(const JSCallbackInfo & args)381 void JSScroller::GetItemIndex(const JSCallbackInfo& args)
382 {
383     if (args.Length() != ARGS_LENGTH) {
384         JSException::Throw(ERROR_CODE_PARAM_INVALID, "%s", "Input parameter length failed.");
385         return;
386     }
387 
388     Dimension xOffset;
389     Dimension yOffset;
390     if (!ConvertFromJSValue(args[0], xOffset) ||
391         !ConvertFromJSValue(args[1], yOffset)) {
392         JSException::Throw(ERROR_CODE_PARAM_INVALID, "%s", "Input parameter check failed.");
393         return;
394     }
395     auto scrollController = controllerWeak_.Upgrade();
396     if (!scrollController) {
397         JSException::Throw(ERROR_CODE_NAMED_ROUTE_ERROR, "%s", "Controller not bound to component.");
398         return;
399     }
400 
401     ContainerScope scope(instanceId_);
402     auto deltaX = xOffset.Value();
403     auto deltaY = yOffset.Value();
404     auto container = Container::Current();
405     if (container) {
406         auto context = container->GetPipelineContext();
407         if (context) {
408             deltaX = context->NormalizeToPx(xOffset);
409             deltaY = context->NormalizeToPx(yOffset);
410         }
411     }
412     int32_t itemIndex = scrollController->GetItemIndex(deltaX, deltaY);
413     auto retVal = JSRef<JSVal>::Make(ToJSValue(itemIndex));
414     args.SetReturnValue(retVal);
415 
416     return;
417 }
418 } // namespace OHOS::Ace::Framework
419