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