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