/* * Copyright (c) 2021-2023 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_indexer.h" #include "base/geometry/dimension.h" #include "base/log/ace_scoring_log.h" #include "base/utils/utils.h" #include "bridge/declarative_frontend/jsview/js_interactable_view.h" #include "bridge/declarative_frontend/jsview/js_scroller.h" #include "bridge/declarative_frontend/jsview/js_view_common_def.h" #include "bridge/declarative_frontend/jsview/models/indexer_model_impl.h" #include "bridge/declarative_frontend/ark_theme/theme_apply/js_indexer_theme.h" #include "core/components/common/layout/constants.h" #include "core/components/common/properties/decoration.h" #include "core/components/common/properties/text_style.h" #include "core/components/indexer/indexer_theme.h" #include "core/components_ng/pattern/indexer/indexer_model_ng.h" namespace OHOS::Ace { std::unique_ptr IndexerModel::instance_ = nullptr; std::mutex IndexerModel::mutex_; IndexerModel* IndexerModel::GetInstance() { if (!instance_) { std::lock_guard lock(mutex_); if (!instance_) { #ifdef NG_BUILD instance_.reset(new NG::IndexerModelNG()); #else if (Container::IsCurrentUseNewPipeline()) { instance_.reset(new NG::IndexerModelNG()); } else { instance_.reset(new Framework::IndexerModelImpl()); } #endif } } return instance_.get(); } } // namespace OHOS::Ace namespace OHOS::Ace::Framework { namespace { const std::vector FONT_STYLES = { FontStyle::NORMAL, FontStyle::ITALIC }; const std::vector ALIGN_STYLE = { V2::AlignStyle::LEFT, V2::AlignStyle::RIGHT, V2::AlignStyle::START, V2::AlignStyle::END }; const std::vector NG_ALIGN_STYLE = { NG::AlignStyle::LEFT, NG::AlignStyle::RIGHT, NG::AlignStyle::START, NG::AlignStyle::END }; constexpr Dimension DEFAULT_ITEM_SIZE = 16.0_vp; constexpr double ZERO_RADIUS = 0.0; constexpr double POPUP_ITEM_DEFAULT_RADIUS = 24.0; constexpr double ITEM_DEFAULT_RADIUS = 8.0; constexpr double RADIUS_OFFSET = 4.0; }; // namespace void JSIndexer::ParseIndexerSelectedObject( const JSCallbackInfo& info, const JSRef& changeEventVal, bool isMethodProp = false) { CHECK_NULL_VOID(changeEventVal->IsFunction()); auto jsFunc = AceType::MakeRefPtr(JSRef(), JSRef::Cast(changeEventVal)); auto changeEvent = [execCtx = info.GetExecutionContext(), func = std::move(jsFunc)](const int32_t selected) { JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx); ACE_SCORING_EVENT("Indexer.SelectedChangeEvent"); auto newJSVal = JSRef::Make(ToJSValue(selected)); func->ExecuteJS(1, &newJSVal); }; if (isMethodProp) { IndexerModel::GetInstance()->SetChangeEvent(changeEvent); } else { IndexerModel::GetInstance()->SetCreatChangeEvent(changeEvent); } } void JSIndexer::Create(const JSCallbackInfo& args) { if (args.Length() < 1 || !args[0]->IsObject()) { return; } size_t length = 0; int32_t selectedVal = 0; std::vector indexerArray; JSRef paramObj = JSRef::Cast(args[0]); JSRef arrayVal = paramObj->GetProperty("arrayValue"); if (arrayVal->IsArray()) { JSRef jsArray = JSRef::Cast(arrayVal); length = jsArray->Length(); for (size_t i = 0; i < length; i++) { auto value = jsArray->GetValueAt(i); if (value->IsString()) { indexerArray.emplace_back(value->ToString()); } } } JSRef selectedProperty = paramObj->GetProperty("selected"); if (selectedProperty->IsNumber()) { selectedVal = selectedProperty->ToNumber(); IndexerModel::GetInstance()->Create(indexerArray, selectedVal); JSIndexerTheme::ApplyTheme(); } else if (length > 0 && selectedProperty->IsObject()) { JSRef selectedObj = JSRef::Cast(selectedProperty); auto selectedValueProperty = selectedObj->GetProperty("value"); if (selectedValueProperty->IsNumber()) { selectedVal = selectedValueProperty->ToNumber(); } IndexerModel::GetInstance()->Create(indexerArray, selectedVal); JSIndexerTheme::ApplyTheme(); JSRef changeEventVal = selectedObj->GetProperty("changeEvent"); if (!changeEventVal.IsEmpty()) { if (!changeEventVal->IsUndefined() && changeEventVal->IsFunction()) { ParseIndexerSelectedObject(args, changeEventVal); } return; } args.ReturnSelf(); } else { IndexerModel::GetInstance()->Create(indexerArray, selectedVal); JSIndexerTheme::ApplyTheme(); } } void JSIndexer::SetSelectedColor(const JSCallbackInfo& args) { if (args.Length() < 1) { return; } IndexerModel::GetInstance()->SetSelectedColor(PaseColor(args)); } void JSIndexer::SetColor(const JSCallbackInfo& args) { if (args.Length() < 1) { return; } IndexerModel::GetInstance()->SetColor(PaseColor(args)); } void JSIndexer::SetPopupColor(const JSCallbackInfo& args) { if (args.Length() < 1) { return; } IndexerModel::GetInstance()->SetPopupColor(PaseColor(args)); } void JSIndexer::SetSelectedBackgroundColor(const JSCallbackInfo& args) { if (args.Length() < 1) { return; } IndexerModel::GetInstance()->SetSelectedBackgroundColor(PaseColor(args)); } void JSIndexer::SetPopupBackground(const JSCallbackInfo& args) { if (args.Length() < 1) { return; } IndexerModel::GetInstance()->SetPopupBackground(PaseColor(args)); } void JSIndexer::SetUsingPopup(bool state) { IndexerModel::GetInstance()->SetUsingPopup(state); } void JSIndexer::SetSelectedFont(const JSCallbackInfo& args) { if (args.Length() < 1) { return; } std::optional fontSize; std::optional fontWeight; std::optional> fontFamily; std::optional fontStyle; if (args[0]->IsObject()) { GetFontContent(args, fontSize, fontWeight, fontFamily, fontStyle); } IndexerModel::GetInstance()->SetSelectedFont(fontSize, fontWeight, fontFamily, fontStyle); } void JSIndexer::SetPopupFont(const JSCallbackInfo& args) { if (args.Length() < 1) { return; } std::optional fontSize; std::optional fontWeight; std::optional> fontFamily; std::optional fontStyle; if (args[0]->IsObject()) { GetFontContent(args, fontSize, fontWeight, fontFamily, fontStyle); } IndexerModel::GetInstance()->SetPopupFont(fontSize, fontWeight, fontFamily, fontStyle); } void JSIndexer::SetFont(const JSCallbackInfo& args) { if (args.Length() < 1) { return; } std::optional fontSize; std::optional fontWeight; std::optional> fontFamily; std::optional fontStyle; if (args[0]->IsObject()) { GetFontContent(args, fontSize, fontWeight, fontFamily, fontStyle); } IndexerModel::GetInstance()->SetFont(fontSize, fontWeight, fontFamily, fontStyle); } void JSIndexer::JsOnSelected(const JSCallbackInfo& args) { if (args[0]->IsFunction()) { auto onSelected = [execCtx = args.GetExecutionContext(), func = JSRef::Cast(args[0])]( const int32_t selected) { JAVASCRIPT_EXECUTION_SCOPE(execCtx); auto params = ConvertToJSValues(selected); func->Call(JSRef(), params.size(), params.data()); }; IndexerModel::GetInstance()->SetOnSelected(onSelected); } } void JSIndexer::JsOnRequestPopupData(const JSCallbackInfo& args) { if (args[0]->IsFunction()) { auto requestPopupData = [execCtx = args.GetExecutionContext(), func = JSRef::Cast(args[0])]( const int32_t selected) { std::vector popupData; JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx, popupData); auto params = ConvertToJSValues(selected); JSRef result = func->Call(JSRef(), params.size(), params.data()); if (result.IsEmpty()) { return popupData; } if (!result->IsArray()) { return popupData; } for (size_t i = 0; i < result->Length(); i++) { if (result->GetValueAt(i)->IsString()) { auto item = result->GetValueAt(i); popupData.emplace_back(item->ToString()); } } return popupData; }; IndexerModel::GetInstance()->SetOnRequestPopupData(requestPopupData); } } void JSIndexer::JsOnPopupSelected(const JSCallbackInfo& args) { if (args[0]->IsFunction()) { auto onPopupSelected = [execCtx = args.GetExecutionContext(), func = JSRef::Cast(args[0])]( const int32_t selected) { JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx); auto params = ConvertToJSValues(selected); func->Call(JSRef(), params.size(), params.data()); }; IndexerModel::GetInstance()->SetOnPopupSelected(onPopupSelected); } } void JSIndexer::GetFontContent(const JSCallbackInfo& args, std::optional& fontSize, std::optional& fontWeight, std::optional>& fontFamily, std::optional& fontStyle) { JSRef obj = JSRef::Cast(args[0]); JSRef size = obj->GetProperty("size"); CalcDimension fontSizeData; if (ParseJsDimensionFp(size, fontSizeData) && !fontSizeData.IsNegative() && fontSizeData.Unit() != DimensionUnit::PERCENT) { fontSize = fontSizeData; } JSRef weight = obj->GetProperty("weight"); if (weight->IsString() || weight->IsNumber()) { fontWeight = ConvertStrToFontWeight(weight->ToString()); } JSRef family = obj->GetProperty("family"); std::vector fontFamilies; if (ParseJsFontFamilies(family, fontFamilies)) { fontFamily = fontFamilies; } JSRef style = obj->GetProperty("style"); if (style->IsNumber()) { int32_t value = style->ToNumber(); if (value >= 0 && value < static_cast(FONT_STYLES.size())) { fontStyle = FONT_STYLES[value]; } } } void JSIndexer::SetItemSize(const JSCallbackInfo& args) { if (args.Length() < 1) { return; } CalcDimension itemSize; if (ParseJsDimensionVp(args[0], itemSize) && GreatNotEqual(itemSize.Value(), 0.0) && itemSize.Unit() != DimensionUnit::PERCENT) { IndexerModel::GetInstance()->SetItemSize(itemSize); return; } IndexerModel::GetInstance()->SetItemSize(DEFAULT_ITEM_SIZE); } void JSIndexer::SetAlignStyle(const JSCallbackInfo& args) { if (args.Length() < 1) { return; } int32_t value = Container::IsCurrentUseNewPipeline() ? static_cast(NG::AlignStyle::END) : static_cast(V2::AlignStyle::END); auto alignValue = -1; if (args[0]->IsNumber()) { alignValue = args[0]->ToNumber(); } if (alignValue >= 0 && alignValue < static_cast(ALIGN_STYLE.size())) { value = alignValue; } IndexerModel::GetInstance()->SetAlignStyle(value); CalcDimension popupHorizontalSpace(-1.0); if (args.Length() > 1) { ParseJsDimensionVp(args[1], popupHorizontalSpace); } IndexerModel::GetInstance()->SetPopupHorizontalSpace(popupHorizontalSpace); } void JSIndexer::SetSelected(const JSCallbackInfo& args) { if (args.Length() >= 1) { int32_t selected = 0; if (ParseJsInteger(args[0], selected)) { IndexerModel::GetInstance()->SetSelected(selected); } if (args.Length() > 1 && args[1]->IsFunction()) { ParseIndexerSelectedObject(args, args[1], true); } } } void JSIndexer::SetPopupPosition(const JSCallbackInfo& args) { std::optional xOpt; std::optional yOpt; if (args[0]->IsObject()) { JSRef obj = JSRef::Cast(args[0]); CalcDimension x; CalcDimension y; JSRef xVal = obj->GetProperty("x"); JSRef yVal = obj->GetProperty("y"); if ((xVal->IsString() && StringUtils::StringToCalcDimensionNG(xVal->ToString(), x, false)) || (!xVal->IsString() && JSViewAbstract::ParseJsDimensionVp(xVal, x))) { xOpt = x; } if ((yVal->IsString() && StringUtils::StringToCalcDimensionNG(yVal->ToString(), y, false)) || (!yVal->IsString() && JSViewAbstract::ParseJsDimensionVp(yVal, y))) { yOpt = y; } } else if (Container::LessThanAPITargetVersion(PlatformVersion::VERSION_TWELVE)) { return; } IndexerModel::GetInstance()->SetPopupPositionX(xOpt); IndexerModel::GetInstance()->SetPopupPositionY(yOpt); } void JSIndexer::SetPopupSelectedColor(const JSCallbackInfo& args) { if (args.Length() < 1) { return; } IndexerModel::GetInstance()->SetPopupSelectedColor(PaseColor(args)); } void JSIndexer::SetPopupUnselectedColor(const JSCallbackInfo& args) { if (args.Length() < 1) { return; } IndexerModel::GetInstance()->SetPopupUnselectedColor(PaseColor(args)); } void JSIndexer::SetPopupItemFont(const JSCallbackInfo& args) { CalcDimension fontSize; std::string weight; if (args[0]->IsObject()) { JSRef obj = JSRef::Cast(args[0]); JSRef size = obj->GetProperty("size"); if (!size->IsNull()) { CalcDimension fontSizeData; if (ParseJsDimensionFp(size, fontSizeData) && !fontSizeData.IsNegative() && fontSizeData.Unit() != DimensionUnit::PERCENT) { fontSize = fontSizeData; } } auto jsWeight = obj->GetProperty("weight"); if (!jsWeight->IsNull()) { if (jsWeight->IsNumber()) { weight = std::to_string(jsWeight->ToNumber()); } else { ParseJsString(jsWeight, weight); } } } IndexerModel::GetInstance()->SetFontSize(fontSize); IndexerModel::GetInstance()->SetFontWeight(ConvertStrToFontWeight(weight, FontWeight::MEDIUM)); } void JSIndexer::SetPopupItemBackgroundColor(const JSCallbackInfo& args) { if (args.Length() < 1) { return; } IndexerModel::GetInstance()->SetPopupItemBackground(PaseColor(args)); } std::optional JSIndexer::PaseColor(const JSCallbackInfo& args) { std::optional colorOpt; Color color; if (ParseJsColor(args[0], color)) { colorOpt = color; } return colorOpt; } void JSIndexer::SetAutoCollapse(const JSCallbackInfo& args) { bool state = true; if (args[0]->IsBoolean()) { state = args[0]->ToBoolean(); } IndexerModel::GetInstance()->SetAutoCollapse(state); } void JSIndexer::SetPopupItemBorderRadius(const JSCallbackInfo& args) { auto radius = Dimension(ZERO_RADIUS, DimensionUnit::VP); auto popupRadius = Dimension(ZERO_RADIUS, DimensionUnit::VP); if (args.Length() > 0 && args[0]->IsNumber()) { auto radiusValue = args[0]->ToNumber(); if (radiusValue >= 0) { radius.SetValue(radiusValue); radius.SetUnit(DimensionUnit::VP); popupRadius.SetValue(radiusValue + RADIUS_OFFSET); popupRadius.SetUnit(DimensionUnit::VP); } } else { radius.SetValue(POPUP_ITEM_DEFAULT_RADIUS); radius.SetUnit(DimensionUnit::VP); popupRadius.SetValue(radius.Value() + RADIUS_OFFSET); popupRadius.SetUnit(DimensionUnit::VP); } IndexerModel::GetInstance()->SetPopupItemBorderRadius(radius); IndexerModel::GetInstance()->SetPopupBorderRadius(popupRadius); } void JSIndexer::SetItemBorderRadius(const JSCallbackInfo& args) { auto radius = Dimension(ZERO_RADIUS, DimensionUnit::VP); auto indexerRadius = Dimension(ZERO_RADIUS, DimensionUnit::VP); if (args.Length() > 0 && args[0]->IsNumber()) { auto radiusValue = args[0]->ToNumber(); if (radiusValue >= 0) { radius.SetValue(radiusValue); radius.SetUnit(DimensionUnit::VP); indexerRadius.SetValue(radiusValue + RADIUS_OFFSET); indexerRadius.SetUnit(DimensionUnit::VP); } } else { radius.SetValue(ITEM_DEFAULT_RADIUS); radius.SetUnit(DimensionUnit::VP); indexerRadius.SetValue(radius.Value() + RADIUS_OFFSET); indexerRadius.SetUnit(DimensionUnit::VP); } IndexerModel::GetInstance()->SetItemBorderRadius(radius); IndexerModel::GetInstance()->SetIndexerBorderRadius(indexerRadius); } void JSIndexer::SetPopupBackgroundBlurStyle(const JSCallbackInfo& args) { if (args.Length() < 1) { return; } BlurStyleOption styleOption; if (args[0]->IsNumber()) { auto blurStyle = args[0]->ToNumber(); if (blurStyle >= static_cast(BlurStyle::NO_MATERIAL) && blurStyle <= static_cast(BlurStyle::COMPONENT_ULTRA_THICK)) { styleOption.blurStyle = static_cast(blurStyle); } else { styleOption.blurStyle = BlurStyle::COMPONENT_REGULAR; } } else { styleOption.blurStyle = BlurStyle::COMPONENT_REGULAR; } IndexerModel::GetInstance()->SetPopupBackgroundBlurStyle(styleOption); } void JSIndexer::SetPopupTitleBackground(const JSCallbackInfo& args) { if (args.Length() < 1) { return; } IndexerModel::GetInstance()->SetPopupTitleBackground(PaseColor(args)); } void JSIndexer::SetWidth(const JSCallbackInfo& args) { JSViewAbstract::JsWidth(args); if (args[0]->IsString() && args[0]->ToString() == "auto") { IndexerModel::GetInstance()->SetAdaptiveWidth(true); } else { IndexerModel::GetInstance()->SetAdaptiveWidth(false); } } void JSIndexer::SetEnableHapticFeedback(const JSCallbackInfo& args) { bool state = true; if (args.Length() > 0 && args[0]->IsBoolean()) { state = args[0]->ToBoolean(); } IndexerModel::GetInstance()->SetEnableHapticFeedback(state); } void JSIndexer::JSBind(BindingTarget globalObj) { MethodOptions opt = MethodOptions::NONE; JSClass::Declare("AlphabetIndexer"); JSClass::StaticMethod("create", &JSIndexer::Create); // API7 onSelected deprecated JSClass::StaticMethod("onSelected", &JSIndexer::JsOnSelected); JSClass::StaticMethod("onSelect", &JSIndexer::JsOnSelected); JSClass::StaticMethod("color", &JSIndexer::SetColor, opt); JSClass::StaticMethod("selectedColor", &JSIndexer::SetSelectedColor, opt); JSClass::StaticMethod("popupColor", &JSIndexer::SetPopupColor, opt); JSClass::StaticMethod("selectedBackgroundColor", &JSIndexer::SetSelectedBackgroundColor, opt); JSClass::StaticMethod("popupBackground", &JSIndexer::SetPopupBackground, opt); JSClass::StaticMethod("usingPopup", &JSIndexer::SetUsingPopup, opt); JSClass::StaticMethod("selectedFont", &JSIndexer::SetSelectedFont); JSClass::StaticMethod("font", &JSIndexer::SetFont); JSClass::StaticMethod("popupFont", &JSIndexer::SetPopupFont); JSClass::StaticMethod("itemSize", &JSIndexer::SetItemSize, opt); JSClass::StaticMethod("alignStyle", &JSIndexer::SetAlignStyle, opt); JSClass::StaticMethod("onRequestPopupData", &JSIndexer::JsOnRequestPopupData, opt); JSClass::StaticMethod("selected", &JSIndexer::SetSelected, opt); JSClass::StaticMethod("popupPosition", &JSIndexer::SetPopupPosition, opt); JSClass::StaticMethod("popupSelectedColor", &JSIndexer::SetPopupSelectedColor, opt); JSClass::StaticMethod("popupUnselectedColor", &JSIndexer::SetPopupUnselectedColor, opt); JSClass::StaticMethod("popupItemFont", &JSIndexer::SetPopupItemFont); JSClass::StaticMethod("popupItemBackgroundColor", &JSIndexer::SetPopupItemBackgroundColor, opt); JSClass::StaticMethod("autoCollapse", &JSIndexer::SetAutoCollapse, opt); JSClass::StaticMethod("popupItemBorderRadius", &JSIndexer::SetPopupItemBorderRadius); JSClass::StaticMethod("itemBorderRadius", &JSIndexer::SetItemBorderRadius); JSClass::StaticMethod("popupBackgroundBlurStyle", &JSIndexer::SetPopupBackgroundBlurStyle); JSClass::StaticMethod("popupTitleBackground", &JSIndexer::SetPopupTitleBackground, opt); JSClass::StaticMethod("width", &JSIndexer::SetWidth); JSClass::StaticMethod("enableHapticFeedback", &JSIndexer::SetEnableHapticFeedback, opt); // keep compatible, need remove after JSClass::StaticMethod("onPopupSelected", &JSIndexer::JsOnPopupSelected, opt); JSClass::StaticMethod("onPopupSelect", &JSIndexer::JsOnPopupSelected, opt); JSClass::StaticMethod("onAttach", &JSInteractableView::JsOnAttach); JSClass::StaticMethod("onAppear", &JSInteractableView::JsOnAppear); JSClass::StaticMethod("onDetach", &JSInteractableView::JsOnDetach); JSClass::StaticMethod("onDisAppear", &JSInteractableView::JsOnDisAppear); JSClass::StaticMethod("onTouch", &JSInteractableView::JsOnTouch); JSClass::InheritAndBind(globalObj); } } // namespace OHOS::Ace::Framework