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