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_scroll.h"
17
18 #include "base/utils/utils.h"
19 #include "bridge/declarative_frontend/jsview/js_scroller.h"
20 #include "bridge/declarative_frontend/jsview/js_view_common_def.h"
21 #include "bridge/declarative_frontend/jsview/models/scroll_model_impl.h"
22 #include "core/components/common/layout/constants.h"
23 #include "core/components/scroll/scrollable.h"
24 #include "core/components_ng/pattern/scroll/inner/scroll_bar.h"
25 #include "core/components_ng/pattern/scroll/scroll_model.h"
26 #include "core/components_ng/pattern/scroll/scroll_model_ng.h"
27
28 namespace OHOS::Ace {
29
30 std::unique_ptr<ScrollModel> ScrollModel::instance_ = nullptr;
31 std::mutex ScrollModel::mutex_;
32
GetInstance()33 ScrollModel* ScrollModel::GetInstance()
34 {
35 if (!instance_) {
36 std::lock_guard<std::mutex> lock(mutex_);
37 if (!instance_) {
38 #ifdef NG_BUILD
39 instance_.reset(new NG::ScrollModelNG());
40 #else
41 if (Container::IsCurrentUseNewPipeline()) {
42 instance_.reset(new NG::ScrollModelNG());
43 } else {
44 instance_.reset(new Framework::ScrollModelImpl());
45 }
46 #endif
47 }
48 }
49 return instance_.get();
50 }
51
52 } // namespace OHOS::Ace
53
54 namespace OHOS::Ace::Framework {
55 namespace {
56 const std::vector<Axis> AXIS = { Axis::VERTICAL, Axis::HORIZONTAL, Axis::FREE, Axis::NONE };
57
ParseJsDimensionArray(const JSRef<JSVal> & jsValue,std::vector<Dimension> & result)58 bool ParseJsDimensionArray(const JSRef<JSVal>& jsValue, std::vector<Dimension>& result)
59 {
60 if (!jsValue->IsArray()) {
61 LOGE("args is not array orobject!");
62 return false;
63 }
64 JSRef<JSArray> array = JSRef<JSArray>::Cast(jsValue);
65 for (size_t i = 0; i < array->Length(); i++) {
66 JSRef<JSVal> value = array->GetValueAt(i);
67 CalcDimension dimension;
68 if (JSViewAbstract::ParseJsDimensionVp(value, dimension)) {
69 result.emplace_back(static_cast<Dimension>(dimension));
70 } else {
71 return false;
72 }
73 }
74 return true;
75 }
76
CheckSnapPaginations(std::vector<Dimension> snapPaginations)77 bool CheckSnapPaginations(std::vector<Dimension> snapPaginations)
78 {
79 CHECK_NULL_RETURN_NOLOG(!snapPaginations.empty(), false);
80 float preValue = (*snapPaginations.begin()).Value();
81 CHECK_NULL_RETURN_NOLOG(!Negative(preValue), false);
82 auto unit = (*snapPaginations.begin()).Unit();
83 for (auto iter = snapPaginations.begin() + 1; iter < snapPaginations.end(); ++iter) {
84 if (Negative((*iter).Value()) || (*iter).Unit() != unit || LessOrEqual((*iter).Value(), preValue)) {
85 LOGE("Invalid snapPagination");
86 return false;
87 }
88 preValue = (*iter).Value();
89 }
90 return true;
91 }
92 } // namespace
93
Create(const JSCallbackInfo & info)94 void JSScroll::Create(const JSCallbackInfo& info)
95 {
96 ScrollModel::GetInstance()->Create();
97 if (info.Length() > 0 && info[0]->IsObject()) {
98 JSScroller* jsScroller = JSRef<JSObject>::Cast(info[0])->Unwrap<JSScroller>();
99 if (jsScroller) {
100 auto positionController = ScrollModel::GetInstance()->GetOrCreateController();
101 jsScroller->SetController(positionController);
102 // Init scroll bar proxy.
103 auto proxy = jsScroller->GetScrollBarProxy();
104 if (!proxy) {
105 proxy = ScrollModel::GetInstance()->CreateScrollBarProxy();
106 jsScroller->SetScrollBarProxy(proxy);
107 }
108 ScrollModel::GetInstance()->SetScrollBarProxy(proxy);
109 }
110 }
111 // init scroll bar
112 std::pair<bool, Color> barColor;
113 barColor.first = false;
114 std::pair<bool, Dimension> barWidth;
115 barWidth.first = false;
116 ScrollModel::GetInstance()->InitScrollBar(GetTheme<ScrollBarTheme>(), barColor, barWidth, EdgeEffect::NONE);
117 }
118
SetScrollable(int32_t value)119 void JSScroll::SetScrollable(int32_t value)
120 {
121 if (value < 0 || value >= static_cast<int32_t>(AXIS.size())) {
122 LOGE("value is not valid: %{public}d", value);
123 return;
124 }
125 ScrollModel::GetInstance()->SetAxis(AXIS[value]);
126 }
127
SetScrollEnabled(const JSCallbackInfo & args)128 void JSScroll::SetScrollEnabled(const JSCallbackInfo& args)
129 {
130 ScrollModel::GetInstance()->SetScrollEnabled(args[0]->IsBoolean() ? args[0]->ToBoolean() : true);
131 }
132
OnScrollBeginCallback(const JSCallbackInfo & args)133 void JSScroll::OnScrollBeginCallback(const JSCallbackInfo& args)
134 {
135 if (args[0]->IsFunction()) {
136 auto onScrollBegin = [execCtx = args.GetExecutionContext(), func = JSRef<JSFunc>::Cast(args[0])](
137 const Dimension& dx, const Dimension& dy) -> ScrollInfo {
138 ScrollInfo scrollInfo { .dx = dx, .dy = dy };
139 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx, scrollInfo);
140 auto params = ConvertToJSValues(dx, dy);
141 auto result = func->Call(JSRef<JSObject>(), params.size(), params.data());
142 if (result.IsEmpty()) {
143 LOGE("Error calling onScrollBegin, result is empty.");
144 return scrollInfo;
145 }
146
147 if (!result->IsObject()) {
148 LOGE("Error calling onScrollBegin, result is not object.");
149 return scrollInfo;
150 }
151
152 auto resObj = JSRef<JSObject>::Cast(result);
153 auto dxRemainValue = resObj->GetProperty("dxRemain");
154 if (dxRemainValue->IsNumber()) {
155 scrollInfo.dx = Dimension(dxRemainValue->ToNumber<float>(), DimensionUnit::VP);
156 }
157 auto dyRemainValue = resObj->GetProperty("dyRemain");
158 if (dyRemainValue->IsNumber()) {
159 scrollInfo.dy = Dimension(dyRemainValue->ToNumber<float>(), DimensionUnit::VP);
160 }
161 return scrollInfo;
162 };
163 ScrollModel::GetInstance()->SetOnScrollBegin(std::move(onScrollBegin));
164 }
165 args.SetReturnValue(args.This());
166 }
167
OnScrollFrameBeginCallback(const JSCallbackInfo & args)168 void JSScroll::OnScrollFrameBeginCallback(const JSCallbackInfo& args)
169 {
170 if (args[0]->IsFunction()) {
171 auto onScrollFrameBegin = [execCtx = args.GetExecutionContext(), func = JSRef<JSFunc>::Cast(args[0])](
172 const Dimension& offset, ScrollState state) -> ScrollFrameResult {
173 OHOS::Ace::ScrollFrameResult scrollRes { .offset = offset };
174 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx, scrollRes);
175 auto params = ConvertToJSValues(offset, state);
176 auto result = func->Call(JSRef<JSObject>(), params.size(), params.data());
177 if (result.IsEmpty()) {
178 LOGE("Error calling onScrollBegin, result is empty.");
179 return scrollRes;
180 }
181
182 if (!result->IsObject()) {
183 LOGE("Error calling onScrollBegin, result is not object.");
184 return scrollRes;
185 }
186
187 auto resObj = JSRef<JSObject>::Cast(result);
188 auto dxRemainValue = resObj->GetProperty("offsetRemain");
189 if (dxRemainValue->IsNumber()) {
190 scrollRes.offset = Dimension(dxRemainValue->ToNumber<float>(), DimensionUnit::VP);
191 }
192 return scrollRes;
193 };
194 ScrollModel::GetInstance()->SetOnScrollFrameBegin(std::move(onScrollFrameBegin));
195 }
196 args.SetReturnValue(args.This());
197 }
198
OnScrollCallback(const JSCallbackInfo & args)199 void JSScroll::OnScrollCallback(const JSCallbackInfo& args)
200 {
201 if (args[0]->IsFunction()) {
202 auto onScroll = [execCtx = args.GetExecutionContext(), func = JSRef<JSFunc>::Cast(args[0])](
203 const Dimension& xOffset, const Dimension& yOffset) {
204 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
205 auto params = ConvertToJSValues(xOffset, yOffset);
206 func->Call(JSRef<JSObject>(), params.size(), params.data());
207 };
208 ScrollModel::GetInstance()->SetOnScroll(std::move(onScroll));
209 }
210 args.SetReturnValue(args.This());
211 }
212
OnScrollEdgeCallback(const JSCallbackInfo & args)213 void JSScroll::OnScrollEdgeCallback(const JSCallbackInfo& args)
214 {
215 if (args[0]->IsFunction()) {
216 auto scrollEdge = [execCtx = args.GetExecutionContext(), func = JSRef<JSFunc>::Cast(args[0])](
217 const NG::ScrollEdge& side) {
218 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
219 auto params = ConvertToJSValues(side);
220 func->Call(JSRef<JSObject>(), 1, params.data());
221 };
222 ScrollModel::GetInstance()->SetOnScrollEdge(std::move(scrollEdge));
223 }
224 args.SetReturnValue(args.This());
225 }
226
OnScrollEndCallback(const JSCallbackInfo & args)227 void JSScroll::OnScrollEndCallback(const JSCallbackInfo& args)
228 {
229 if (args[0]->IsFunction()) {
230 auto scrollEnd = [execCtx = args.GetExecutionContext(), func = JSRef<JSFunc>::Cast(args[0])]() {
231 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
232 func->Call(JSRef<JSObject>(), 0, nullptr);
233 };
234 ScrollModel::GetInstance()->SetOnScrollEnd(std::move(scrollEnd));
235 }
236 args.SetReturnValue(args.This());
237 }
238
OnScrollStartCallback(const JSCallbackInfo & args)239 void JSScroll::OnScrollStartCallback(const JSCallbackInfo& args)
240 {
241 if (args[0]->IsFunction()) {
242 auto scrollStart = [execCtx = args.GetExecutionContext(), func = JSRef<JSFunc>::Cast(args[0])]() {
243 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
244 func->Call(JSRef<JSObject>(), 0, nullptr);
245 };
246 ScrollModel::GetInstance()->SetOnScrollStart(std::move(scrollStart));
247 }
248 args.SetReturnValue(args.This());
249 }
250
OnScrollStopCallback(const JSCallbackInfo & args)251 void JSScroll::OnScrollStopCallback(const JSCallbackInfo& args)
252 {
253 if (args[0]->IsFunction()) {
254 auto scrollStop = [execCtx = args.GetExecutionContext(), func = JSRef<JSFunc>::Cast(args[0])]() {
255 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
256 func->Call(JSRef<JSObject>(), 0, nullptr);
257 };
258 ScrollModel::GetInstance()->SetOnScrollStop(std::move(scrollStop));
259 }
260 args.SetReturnValue(args.This());
261 }
262
JSBind(BindingTarget globalObj)263 void JSScroll::JSBind(BindingTarget globalObj)
264 {
265 JSClass<JSScroll>::Declare("Scroll");
266 MethodOptions opt = MethodOptions::NONE;
267 JSClass<JSScroll>::StaticMethod("create", &JSScroll::Create, opt);
268 JSClass<JSScroll>::StaticMethod("scrollable", &JSScroll::SetScrollable, opt);
269 JSClass<JSScroll>::StaticMethod("onScrollBegin", &JSScroll::OnScrollBeginCallback, opt);
270 JSClass<JSScroll>::StaticMethod("onScrollFrameBegin", &JSScroll::OnScrollFrameBeginCallback, opt);
271 JSClass<JSScroll>::StaticMethod("onScroll", &JSScroll::OnScrollCallback, opt);
272 JSClass<JSScroll>::StaticMethod("onScrollEdge", &JSScroll::OnScrollEdgeCallback, opt);
273 JSClass<JSScroll>::StaticMethod("onScrollEnd", &JSScroll::OnScrollEndCallback, opt);
274 JSClass<JSScroll>::StaticMethod("onScrollStart", &JSScroll::OnScrollStartCallback, opt);
275 JSClass<JSScroll>::StaticMethod("onScrollStop", &JSScroll::OnScrollStopCallback, opt);
276 JSClass<JSScroll>::StaticMethod("onClick", &JSInteractableView::JsOnClick);
277 JSClass<JSScroll>::StaticMethod("onTouch", &JSInteractableView::JsOnTouch);
278 JSClass<JSScroll>::StaticMethod("onHover", &JSInteractableView::JsOnHover);
279 JSClass<JSScroll>::StaticMethod("onKeyEvent", &JSInteractableView::JsOnKey);
280 JSClass<JSScroll>::StaticMethod("onDeleteEvent", &JSInteractableView::JsOnDelete);
281 JSClass<JSScroll>::StaticMethod("onAppear", &JSInteractableView::JsOnAppear);
282 JSClass<JSScroll>::StaticMethod("onDisAppear", &JSInteractableView::JsOnDisAppear);
283 JSClass<JSScroll>::StaticMethod("edgeEffect", &JSScroll::SetEdgeEffect, opt);
284 JSClass<JSScroll>::StaticMethod("scrollBar", &JSScroll::SetScrollBar, opt);
285 JSClass<JSScroll>::StaticMethod("scrollBarColor", &JSScroll::SetScrollBarColor, opt);
286 JSClass<JSScroll>::StaticMethod("scrollBarWidth", &JSScroll::SetScrollBarWidth, opt);
287 JSClass<JSScroll>::StaticMethod("remoteMessage", &JSInteractableView::JsCommonRemoteMessage);
288 JSClass<JSScroll>::StaticMethod("width", &JSScroll::JsWidth);
289 JSClass<JSScroll>::StaticMethod("height", &JSScroll::JsHeight);
290 JSClass<JSScroll>::StaticMethod("nestedScroll", &JSScroll::SetNestedScroll);
291 JSClass<JSScroll>::StaticMethod("enableScrollInteraction", &JSScroll::SetScrollEnabled);
292 JSClass<JSScroll>::StaticMethod("friction", &JSScroll::SetFriction);
293 JSClass<JSScroll>::StaticMethod("scrollSnap", &JSScroll::SetScrollSnap);
294 JSClass<JSScroll>::InheritAndBind<JSContainerBase>(globalObj);
295 }
296
SetScrollBar(const JSCallbackInfo & args)297 void JSScroll::SetScrollBar(const JSCallbackInfo& args)
298 {
299 if (args.Length() < 1) {
300 LOGE("args is invalid");
301 return;
302 }
303 int32_t displayMode;
304 if (args[0]->IsNull() || args[0]->IsUndefined() || !ParseJsInt32(args[0], displayMode)) {
305 displayMode = static_cast<int32_t>(NG::DisplayMode::AUTO);
306 }
307 ScrollModel::GetInstance()->SetDisplayMode(displayMode);
308 }
309
SetScrollBarWidth(const JSCallbackInfo & args)310 void JSScroll::SetScrollBarWidth(const JSCallbackInfo& args)
311 {
312 auto pipelineContext = PipelineContext::GetCurrentContext();
313 CHECK_NULL_VOID_NOLOG(pipelineContext);
314 auto theme = pipelineContext->GetTheme<ScrollBarTheme>();
315 CHECK_NULL_VOID_NOLOG(theme);
316 CalcDimension scrollBarWidth;
317 if (args.Length() < 1) {
318 LOGE("args is invalid");
319 return;
320 }
321 if (!ParseJsDimensionVp(args[0], scrollBarWidth) || args[0]->IsNull() || args[0]->IsUndefined() ||
322 (args[0]->IsString() && args[0]->ToString().empty()) || LessNotEqual(scrollBarWidth.Value(), 0.0) ||
323 scrollBarWidth.Unit() == DimensionUnit::PERCENT) {
324 scrollBarWidth = theme->GetNormalWidth();
325 }
326 ScrollModel::GetInstance()->SetScrollBarWidth(scrollBarWidth);
327 }
328
SetScrollBarColor(const std::string & scrollBarColor)329 void JSScroll::SetScrollBarColor(const std::string& scrollBarColor)
330 {
331 if (scrollBarColor.empty()) {
332 return;
333 }
334 auto pipelineContext = PipelineContext::GetCurrentContext();
335 CHECK_NULL_VOID_NOLOG(pipelineContext);
336 auto theme = pipelineContext->GetTheme<ScrollBarTheme>();
337 CHECK_NULL_VOID_NOLOG(theme);
338 Color color(theme->GetForegroundColor());
339 Color::ParseColorString(scrollBarColor, color);
340 ScrollModel::GetInstance()->SetScrollBarColor(color);
341 }
342
SetEdgeEffect(const JSCallbackInfo & args)343 void JSScroll::SetEdgeEffect(const JSCallbackInfo& args)
344 {
345 if (args.Length() < 1) {
346 LOGE("args is invalid");
347 return;
348 }
349 int32_t edgeEffect;
350 if (args[0]->IsNull() || args[0]->IsUndefined() || !ParseJsInt32(args[0], edgeEffect) ||
351 edgeEffect < static_cast<int32_t>(EdgeEffect::SPRING) || edgeEffect > static_cast<int32_t>(EdgeEffect::NONE)) {
352 edgeEffect = static_cast<int32_t>(EdgeEffect::NONE);
353 }
354 ScrollModel::GetInstance()->SetEdgeEffect(static_cast<EdgeEffect>(edgeEffect));
355 }
356
JsWidth(const JSCallbackInfo & info)357 void JSScroll::JsWidth(const JSCallbackInfo& info)
358 {
359 JSViewAbstract::JsWidth(info);
360 ScrollModel::GetInstance()->SetHasWidth(true);
361 }
362
JsHeight(const JSCallbackInfo & info)363 void JSScroll::JsHeight(const JSCallbackInfo& info)
364 {
365 JSViewAbstract::JsHeight(info);
366 ScrollModel::GetInstance()->SetHasHeight(true);
367 }
368
SetNestedScroll(const JSCallbackInfo & args)369 void JSScroll::SetNestedScroll(const JSCallbackInfo& args)
370 {
371 NestedScrollOptions nestedOpt = {
372 .forward = NestedScrollMode::SELF_ONLY,
373 .backward = NestedScrollMode::SELF_ONLY,
374 };
375 if (args.Length() < 1 || !args[0]->IsObject()) {
376 ScrollModel::GetInstance()->SetNestedScroll(nestedOpt);
377 LOGW("Invalid params");
378 return;
379 }
380 JSRef<JSObject> obj = JSRef<JSObject>::Cast(args[0]);
381 int32_t froward = 0;
382 JSViewAbstract::ParseJsInt32(obj->GetProperty("scrollForward"), froward);
383 if (froward < static_cast<int32_t>(NestedScrollMode::SELF_ONLY) ||
384 froward > static_cast<int32_t>(NestedScrollMode::PARALLEL)) {
385 LOGW("ScrollFroward params invalid");
386 froward = 0;
387 }
388 int32_t backward = 0;
389 JSViewAbstract::ParseJsInt32(obj->GetProperty("scrollBackward"), backward);
390 if (backward < static_cast<int32_t>(NestedScrollMode::SELF_ONLY) ||
391 backward > static_cast<int32_t>(NestedScrollMode::PARALLEL)) {
392 LOGW("ScrollFroward params invalid");
393 backward = 0;
394 }
395 nestedOpt.forward = static_cast<NestedScrollMode>(froward);
396 nestedOpt.backward = static_cast<NestedScrollMode>(backward);
397 ScrollModel::GetInstance()->SetNestedScroll(nestedOpt);
398 args.ReturnSelf();
399 }
400
SetFriction(const JSCallbackInfo & info)401 void JSScroll::SetFriction(const JSCallbackInfo& info)
402 {
403 double friction = -1.0;
404 if (!JSViewAbstract::ParseJsDouble(info[0], friction)) {
405 LOGW("Friction params invalid,can not convert to double");
406 friction = -1.0;
407 }
408 ScrollModel::GetInstance()->SetFriction(friction);
409 }
410
SetScrollSnap(const JSCallbackInfo & args)411 void JSScroll::SetScrollSnap(const JSCallbackInfo& args)
412 {
413 if (args.Length() < 1 || !args[0]->IsObject()) {
414 LOGW("Invalid params");
415 return;
416 }
417 JSRef<JSObject> obj = JSRef<JSObject>::Cast(args[0]);
418 auto snapAlignValue = obj->GetProperty("snapAlign");
419 int32_t snapAlign = static_cast<int32_t>(ScrollSnapAlign::NONE);
420 if (snapAlignValue->IsNull() || snapAlignValue->IsUndefined() || !ParseJsInt32(snapAlignValue, snapAlign) ||
421 snapAlign < static_cast<int32_t>(ScrollSnapAlign::NONE) ||
422 snapAlign > static_cast<int32_t>(ScrollSnapAlign::END)) {
423 snapAlign = static_cast<int32_t>(ScrollSnapAlign::NONE);
424 }
425
426 auto paginationValue = obj->GetProperty("snapPagination");
427 CalcDimension intervalSize;
428 std::vector<Dimension> snapPaginations;
429 if (!ParseJsDimensionVp(paginationValue, intervalSize) || intervalSize.IsNegative()) {
430 intervalSize = CalcDimension(0.0);
431 }
432 if (!ParseJsDimensionArray(paginationValue, snapPaginations) || !CheckSnapPaginations(snapPaginations)) {
433 std::vector<Dimension>().swap(snapPaginations);
434 }
435
436 bool enableSnapToStart = true;
437 bool enableSnapToEnd = true;
438 ParseJsBool(obj->GetProperty("enableSnapToStart"), enableSnapToStart);
439 ParseJsBool(obj->GetProperty("enableSnapToEnd"), enableSnapToEnd);
440 std::pair<bool, bool> enableSnapToSide = { enableSnapToStart, enableSnapToEnd };
441 ScrollModel::GetInstance()->SetScrollSnap(
442 static_cast<ScrollSnapAlign>(snapAlign), intervalSize, snapPaginations, enableSnapToSide);
443 }
444 } // namespace OHOS::Ace::Framework
445