• 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/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