• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 } // 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("scrollPage", &JSScroller::ScrollPage);
73     JSClass<JSScroller>::CustomMethod("currentOffset", &JSScroller::CurrentOffset);
74     JSClass<JSScroller>::CustomMethod("scrollToIndex", &JSScroller::ScrollToIndex);
75     JSClass<JSScroller>::CustomMethod("scrollBy", &JSScroller::ScrollBy);
76     JSClass<JSScroller>::CustomMethod("isAtEnd", &JSScroller::IsAtEnd);
77     JSClass<JSScroller>::CustomMethod("getItemRect", &JSScroller::GetItemRect);
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 
CreateRectangle(const Rect & info)95 JSRef<JSObject> JSScroller::CreateRectangle(const Rect& info)
96 {
97     JSRef<JSObject> rectObj = JSRef<JSObject>::New();
98     rectObj->SetProperty<double>("x", info.Left());
99     rectObj->SetProperty<double>("y", info.Top());
100     rectObj->SetProperty<double>("width", info.Width());
101     rectObj->SetProperty<double>("height", info.Height());
102     return rectObj;
103 }
104 
ScrollTo(const JSCallbackInfo & args)105 void JSScroller::ScrollTo(const JSCallbackInfo& args)
106 {
107     if (args.Length() < 1 || !args[0]->IsObject()) {
108         return;
109     }
110 
111     JSRef<JSObject> obj = JSRef<JSObject>::Cast(args[0]);
112     Dimension xOffset;
113     Dimension yOffset;
114     auto xOffsetStr = obj->GetProperty("xOffset");
115     auto yOffsetStr = obj->GetProperty("yOffset");
116     if (!std::regex_match(xOffsetStr->ToString(), DIMENSION_REGEX) ||
117         !std::regex_match(yOffsetStr->ToString(), DIMENSION_REGEX) || !ConvertFromJSValue(xOffsetStr, xOffset) ||
118         !ConvertFromJSValue(yOffsetStr, yOffset)) {
119         return;
120     }
121 
122     double duration = 0.0;
123     bool smooth = false;
124     bool canOverScroll = false;
125     RefPtr<Curve> curve = Curves::EASE;
126     auto animationValue = obj->GetProperty("animation");
127     if (animationValue->IsObject()) {
128         auto animationObj = JSRef<JSObject>::Cast(animationValue);
129         if (!ConvertFromJSValue(animationObj->GetProperty("duration"), duration) || Negative(duration)) {
130             duration = DEFAULT_DURATION;
131         }
132 
133         auto curveArgs = animationObj->GetProperty("curve");
134         ParseCurveParams(curve, curveArgs);
135         ConvertFromJSValue(animationObj->GetProperty("canOverScroll"), canOverScroll);
136     } else if (animationValue->IsBoolean()) {
137         smooth = animationValue->ToBoolean();
138     }
139     auto scrollController = controllerWeak_.Upgrade();
140     if (!scrollController) {
141         return;
142     }
143     ContainerScope scope(instanceId_);
144     auto direction = scrollController->GetScrollDirection();
145     auto position = direction == Axis::VERTICAL ? yOffset : xOffset;
146     scrollController->AnimateTo(position, static_cast<float>(duration), curve, smooth, canOverScroll);
147 }
148 
ParseCurveParams(RefPtr<Curve> & curve,const JSRef<JSVal> & jsValue)149 void JSScroller::ParseCurveParams(RefPtr<Curve>& curve, const JSRef<JSVal>& jsValue)
150 {
151     std::string curveName;
152     if (ConvertFromJSValue(jsValue, curveName)) {
153         auto index = BinarySearchFindIndex(CURVE_MAP, ArraySize(CURVE_MAP), curveName.c_str());
154         if (index >= 0) {
155             curve = CURVE_MAP[index].value;
156         }
157     } else if (jsValue->IsObject()) {
158         JSRef<JSVal> curveString = JSRef<JSObject>::Cast(jsValue)->GetProperty("__curveString");
159         if (curveString->IsString()) {
160             curve = CreateCurve(curveString->ToString());
161         }
162     }
163 }
164 
ScrollEdge(const JSCallbackInfo & args)165 void JSScroller::ScrollEdge(const JSCallbackInfo& args)
166 {
167     AlignDeclaration::Edge edge = AlignDeclaration::Edge::AUTO;
168     if (args.Length() < 1 || !ConvertFromJSValue(args[0], EDGE_TABLE, edge)) {
169         return;
170     }
171     auto scrollController = controllerWeak_.Upgrade();
172     if (!scrollController) {
173         return;
174     }
175     ScrollEdgeType edgeType = EDGE_TYPE_TABLE[static_cast<int32_t>(edge)];
176     ContainerScope scope(instanceId_);
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         return;
187     }
188     auto scrollController = controllerWeak_.Upgrade();
189     if (!scrollController) {
190         return;
191     }
192     // 2:parameters count, 1: parameter index
193     if (args.Length() >= 2 && args[1]->IsBoolean()) {
194         smooth = args[1]->ToBoolean();
195     }
196     // 3:parameters count, 2: parameter index
197     if (args.Length() == 3) {
198         ConvertFromJSValue(args[2], ALIGN_TABLE, align);
199     }
200     ContainerScope scope(instanceId_);
201     scrollController->JumpTo(index, smooth, align, SCROLL_FROM_JUMP);
202 }
203 
ScrollPage(const JSCallbackInfo & args)204 void JSScroller::ScrollPage(const JSCallbackInfo& args)
205 {
206     if (args.Length() < 1 || !args[0]->IsObject()) {
207         return;
208     }
209 
210     auto obj = JSRef<JSObject>::Cast(args[0]);
211     bool next = true;
212     if (!ConvertFromJSValue(obj->GetProperty("next"), next)) {
213         return;
214     }
215 
216     Axis direction = Axis::NONE;
217     ConvertFromJSValue(obj->GetProperty("direction"), DIRECTION_TABLE, direction);
218     auto scrollController = controllerWeak_.Upgrade();
219     if (!scrollController) {
220         return;
221     }
222     ContainerScope scope(instanceId_);
223     scrollController->ScrollPage(!next, true);
224 }
225 
CurrentOffset(const JSCallbackInfo & args)226 void JSScroller::CurrentOffset(const JSCallbackInfo& args)
227 {
228     auto scrollController = controllerWeak_.Upgrade();
229     if (!scrollController) {
230         return;
231     }
232     auto retObj = JSRef<JSObject>::New();
233     ContainerScope scope(instanceId_);
234     auto offset = scrollController->GetCurrentOffset();
235     retObj->SetProperty("xOffset", offset.GetX());
236     retObj->SetProperty("yOffset", offset.GetY());
237     args.SetReturnValue(retObj);
238 }
239 
ScrollBy(const JSCallbackInfo & args)240 void JSScroller::ScrollBy(const JSCallbackInfo& args)
241 {
242     if (args.Length() < 2) {
243         return;
244     }
245 
246     Dimension xOffset;
247     Dimension yOffset;
248     if (!ConvertFromJSValue(args[0], xOffset) ||
249         !ConvertFromJSValue(args[1], yOffset)) {
250         return;
251     }
252 
253     auto deltaX = xOffset.Value();
254     auto deltaY = yOffset.Value();
255     auto container = Container::Current();
256     if (container) {
257         auto context = container->GetPipelineContext();
258         if (context) {
259             if (xOffset.Unit() == DimensionUnit::PERCENT) {
260                 deltaX = 0.0;
261             } else {
262                 deltaX = context->NormalizeToPx(xOffset);
263             }
264             if (yOffset.Unit() == DimensionUnit::PERCENT) {
265                 deltaY = 0.0;
266             } else {
267                 deltaY = context->NormalizeToPx(yOffset);
268             }
269         }
270     }
271     auto scrollController = controllerWeak_.Upgrade();
272     if (scrollController) {
273         ContainerScope scope(instanceId_);
274         scrollController->ScrollBy(deltaX, deltaY, false);
275     }
276 }
277 
IsAtEnd(const JSCallbackInfo & args)278 void JSScroller::IsAtEnd(const JSCallbackInfo& args)
279 {
280     auto scrollController = controllerWeak_.Upgrade();
281     if (!scrollController) {
282         return;
283     }
284     ContainerScope scope(instanceId_);
285     bool isAtEnd = scrollController->IsAtEnd();
286     auto retVal = JSRef<JSVal>::Make(ToJSValue(isAtEnd));
287     args.SetReturnValue(retVal);
288 }
289 
GetItemRect(const JSCallbackInfo & args)290 void JSScroller::GetItemRect(const JSCallbackInfo& args)
291 {
292     int32_t index = -1;
293     if (args.Length() != 1 || !ConvertFromJSValue(args[0], index)) {
294         JSException::Throw(ERROR_CODE_PARAM_INVALID, "%s", "Input parameter check failed.");
295         return;
296     }
297     auto scrollController = controllerWeak_.Upgrade();
298     if (scrollController) {
299         ContainerScope scope(instanceId_);
300         auto rectObj = CreateRectangle(scrollController->GetItemRect(index));
301         JSRef<JSVal> rect = JSRef<JSObject>::Cast(rectObj);
302         args.SetReturnValue(rect);
303     } else {
304         JSException::Throw(ERROR_CODE_NAMED_ROUTE_ERROR, "%s", "Controller not bound to component.");
305     }
306 }
307 } // namespace OHOS::Ace::Framework
308