/* * Copyright (c) 2021-2022 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "bridge/declarative_frontend/jsview/js_scroller.h" #include "base/geometry/axis.h" #include "base/utils/linear_map.h" #include "base/utils/utils.h" #include "bridge/declarative_frontend/engine/js_types.h" #include "bridge/declarative_frontend/jsview/js_view_common_def.h" #include "core/animation/curves.h" #include "core/common/container.h" #include "core/components/common/layout/align_declaration.h" namespace OHOS::Ace::Framework { namespace { constexpr Axis DIRECTION_TABLE[] = { Axis::VERTICAL, Axis::HORIZONTAL }; constexpr AlignDeclaration::Edge EDGE_TABLE[] = { AlignDeclaration::Edge::TOP, AlignDeclaration::Edge::CENTER, AlignDeclaration::Edge::BOTTOM, AlignDeclaration::Edge::BASELINE, AlignDeclaration::Edge::START, AlignDeclaration::Edge::MIDDLE, AlignDeclaration::Edge::END, }; // corresponding to EDGE_TABLE[] constexpr ScrollEdgeType EDGE_TYPE_TABLE[] = { ScrollEdgeType::SCROLL_TOP, ScrollEdgeType::SCROLL_NONE, ScrollEdgeType::SCROLL_BOTTOM, ScrollEdgeType::SCROLL_NONE, ScrollEdgeType::SCROLL_TOP, ScrollEdgeType::SCROLL_NONE, ScrollEdgeType::SCROLL_BOTTOM }; const LinearMapNode> CURVE_MAP[] = { { "ease", Curves::EASE }, { "ease-in", Curves::EASE_IN }, { "ease-in-out", Curves::EASE_IN_OUT }, { "ease-out", Curves::EASE_OUT }, { "friction", Curves::FRICTION }, { "linear", Curves::LINEAR }, }; constexpr double DEFAULT_DURATION = 1000.0; constexpr ScrollAlign ALIGN_TABLE[] = { ScrollAlign::START, ScrollAlign::CENTER, ScrollAlign::END, ScrollAlign::AUTO, }; const std::regex DIMENSION_REGEX(R"(^[-+]?\d+(?:\.\d+)?(?:px|vp|fp|lpx)?$)", std::regex::icase); } // namespace void JSScroller::JSBind(BindingTarget globalObj) { JSClass::Declare("Scroller"); JSClass::CustomMethod("scrollTo", &JSScroller::ScrollTo); JSClass::CustomMethod("scrollEdge", &JSScroller::ScrollEdge); JSClass::CustomMethod("scrollPage", &JSScroller::ScrollPage); JSClass::CustomMethod("currentOffset", &JSScroller::CurrentOffset); JSClass::CustomMethod("scrollToIndex", &JSScroller::ScrollToIndex); JSClass::CustomMethod("scrollBy", &JSScroller::ScrollBy); JSClass::CustomMethod("isAtEnd", &JSScroller::IsAtEnd); JSClass::Bind(globalObj, JSScroller::Constructor, JSScroller::Destructor); } void JSScroller::Constructor(const JSCallbackInfo& args) { auto scroller = Referenced::MakeRefPtr(); scroller->IncRefCount(); args.SetReturnValue(Referenced::RawPtr(scroller)); } void JSScroller::Destructor(JSScroller* scroller) { if (scroller != nullptr) { scroller->DecRefCount(); } } void JSScroller::ScrollTo(const JSCallbackInfo& args) { if (args.Length() < 1 || !args[0]->IsObject()) { LOGW("Invalid params"); return; } JSRef obj = JSRef::Cast(args[0]); Dimension xOffset; Dimension yOffset; auto xOffsetStr = obj->GetProperty("xOffset"); auto yOffsetStr = obj->GetProperty("yOffset"); if (!std::regex_match(xOffsetStr->ToString(), DIMENSION_REGEX) || !std::regex_match(yOffsetStr->ToString(), DIMENSION_REGEX) || !ConvertFromJSValue(xOffsetStr, xOffset) || !ConvertFromJSValue(yOffsetStr, yOffset)) { LOGW("Failed to parse param 'xOffset' or 'yOffset'"); return; } double duration = 0.0; bool smooth = false; RefPtr curve = Curves::EASE; auto animationValue = obj->GetProperty("animation"); if (animationValue->IsObject()) { auto animationObj = JSRef::Cast(animationValue); if (!ConvertFromJSValue(animationObj->GetProperty("duration"), duration) || NonPositive(duration)) { LOGW("Failed to parse param 'duration' or it is not a positive number, set it as the default value"); duration = DEFAULT_DURATION; } auto curveArgs = animationObj->GetProperty("curve"); ParseCurveParams(curve, curveArgs); } else if (animationValue->IsBoolean()) { smooth = animationValue->ToBoolean(); } if (GreatNotEqual(duration, 0.0)) { LOGD("ScrollTo(%lf, %lf, %lf)", xOffset.Value(), yOffset.Value(), duration); } else { LOGD("ScrollTo(%lf, %lf)", xOffset.Value(), yOffset.Value()); } auto scrollController = controllerWeak_.Upgrade(); if (!scrollController) { LOGE("controller_ is nullptr"); return; } auto direction = scrollController->GetScrollDirection(); auto position = direction == Axis::VERTICAL ? yOffset : xOffset; scrollController->AnimateTo(position, static_cast(duration), curve, smooth); } void JSScroller::ParseCurveParams(RefPtr& curve, const JSRef& jsValue) { std::string curveName; if (ConvertFromJSValue(jsValue, curveName)) { auto index = BinarySearchFindIndex(CURVE_MAP, ArraySize(CURVE_MAP), curveName.c_str()); if (index >= 0) { curve = CURVE_MAP[index].value; } } else if (jsValue->IsObject()) { auto icurveArgs = JsonUtil::ParseJsonString(jsValue->ToString()); if (icurveArgs->IsObject()) { auto curveString = icurveArgs->GetValue("__curveString"); curve = CreateCurve(curveString->GetString()); } } } void JSScroller::ScrollEdge(const JSCallbackInfo& args) { AlignDeclaration::Edge edge = AlignDeclaration::Edge::AUTO; if (args.Length() < 1 || !ConvertFromJSValue(args[0], EDGE_TABLE, edge)) { LOGW("Invalid params"); return; } auto scrollController = controllerWeak_.Upgrade(); if (!scrollController) { LOGE("controller_ is nullptr"); return; } LOGD("ScrollEdge(%{public}d)", static_cast(edge)); ScrollEdgeType edgeType = EDGE_TYPE_TABLE[static_cast(edge)]; scrollController->ScrollToEdge(edgeType, true); } void JSScroller::ScrollToIndex(const JSCallbackInfo& args) { int32_t index = 0; bool smooth = false; ScrollAlign align = ScrollAlign::NONE; if (args.Length() < 1 || !ConvertFromJSValue(args[0], index) || index < 0) { LOGW("Invalid params"); return; } auto scrollController = controllerWeak_.Upgrade(); if (!scrollController) { LOGE("controller_ is nullptr"); return; } // 2:parameters count, 1: parameter index if (args.Length() >= 2 && args[1]->IsBoolean()) { smooth = args[1]->ToBoolean(); } // 3:parameters count, 2: parameter index if (args.Length() == 3 && !ConvertFromJSValue(args[2], ALIGN_TABLE, align)) { LOGE("Invalid align params"); } scrollController->JumpTo(index, smooth, align, SCROLL_FROM_JUMP); } void JSScroller::ScrollPage(const JSCallbackInfo& args) { if (args.Length() < 1 || !args[0]->IsObject()) { LOGW("Invalid params"); return; } auto obj = JSRef::Cast(args[0]); bool next = true; if (!ConvertFromJSValue(obj->GetProperty("next"), next)) { LOGW("Failed to parse param 'next'"); return; } Axis direction = Axis::NONE; ConvertFromJSValue(obj->GetProperty("direction"), DIRECTION_TABLE, direction); auto scrollController = controllerWeak_.Upgrade(); if (!scrollController) { LOGE("controller_ is nullptr"); return; } LOGD("ScrollPage(%{public}s, %{public}d)", next ? "true" : "false", static_cast(direction)); scrollController->ScrollPage(!next, true); } void JSScroller::CurrentOffset(const JSCallbackInfo& args) { LOGD("CurrentOffset()"); auto scrollController = controllerWeak_.Upgrade(); if (!scrollController) { LOGE("controller_ is nullptr"); return; } auto retObj = JSRef::New(); auto offset = scrollController->GetCurrentOffset(); retObj->SetProperty("xOffset", offset.GetX()); retObj->SetProperty("yOffset", offset.GetY()); args.SetReturnValue(retObj); } void JSScroller::ScrollBy(const JSCallbackInfo& args) { if (args.Length() < 2) { LOGW("Invalid params"); return; } Dimension xOffset; Dimension yOffset; if (!ConvertFromJSValue(args[0], xOffset) || !ConvertFromJSValue(args[1], yOffset)) { LOGW("Failed to parse param"); return; } auto deltaX = xOffset.Value(); auto deltaY = yOffset.Value(); auto container = Container::Current(); if (container) { auto context = container->GetPipelineContext(); if (context) { if (xOffset.Unit() == DimensionUnit::PERCENT) { deltaX = 0.0; } else { deltaX = context->NormalizeToPx(xOffset); } if (yOffset.Unit() == DimensionUnit::PERCENT) { deltaY = 0.0; } else { deltaY = context->NormalizeToPx(yOffset); } } } auto scrollController = controllerWeak_.Upgrade(); if (scrollController) { scrollController->ScrollBy(deltaX, deltaY, false); } } void JSScroller::IsAtEnd(const JSCallbackInfo& args) { auto scrollController = controllerWeak_.Upgrade(); if (!scrollController) { LOGE("controller_ is nullptr"); return; } bool isAtEnd = scrollController->IsAtEnd(); auto retVal = JSRef::Make(ToJSValue(isAtEnd)); args.SetReturnValue(retVal); } } // namespace OHOS::Ace::Framework