1 /*
2  * Copyright (c) 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_menu_item.h"
17 
18 #include "base/log/ace_scoring_log.h"
19 #include "bridge/declarative_frontend/jsview/models/menu_item_model_impl.h"
20 #include "core/components_ng/base/view_stack_model.h"
21 #include "core/components_ng/base/view_stack_processor.h"
22 #include "core/components_ng/pattern/menu/menu_item/menu_item_model.h"
23 #include "core/components_ng/pattern/menu/menu_item/menu_item_model_ng.h"
24 #include "bridge/declarative_frontend/ark_theme/theme_apply/js_menu_item_theme.h"
25 #include "core/components_ng/pattern/symbol/symbol_source_info.h"
26 
27 namespace OHOS::Ace {
28 std::unique_ptr<MenuItemModel> MenuItemModel::instance_ = nullptr;
29 std::mutex MenuItemModel::mutex_;
30 
GetInstance()31 MenuItemModel* MenuItemModel::GetInstance()
32 {
33     if (!instance_) {
34         std::lock_guard<std::mutex> lock(mutex_);
35         if (!instance_) {
36 #ifdef NG_BUILD
37             instance_.reset(new NG::MenuItemModelNG());
38 #else
39             if (Container::IsCurrentUseNewPipeline()) {
40                 instance_.reset(new NG::MenuItemModelNG());
41             } else {
42                 instance_.reset(new Framework::MenuItemModelImpl());
43             }
44 #endif
45         }
46     }
47     return instance_.get();
48 }
49 } // namespace OHOS::Ace
50 
51 namespace OHOS::Ace::Framework {
Create(const JSCallbackInfo & info)52 void JSMenuItem::Create(const JSCallbackInfo& info)
53 {
54     if (info.Length() < 1 || (!info[0]->IsObject() && !info[0]->IsFunction())) {
55         MenuItemModel::GetInstance()->Create(nullptr);
56         return;
57     }
58     // custom menu item
59     if (info[0]->IsFunction()) {
60         auto builderFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSFunc>::Cast(info[0]));
61         CHECK_NULL_VOID(builderFunc);
62         RefPtr<NG::UINode> customNode;
63         {
64             ViewStackModel::GetInstance()->NewScope();
65             builderFunc->Execute();
66             customNode = AceType::DynamicCast<NG::UINode>(ViewStackModel::GetInstance()->Finish());
67         }
68         CHECK_NULL_VOID(customNode);
69         MenuItemModel::GetInstance()->Create(customNode);
70     } else {
71         auto menuItemObj = JSRef<JSObject>::Cast(info[0]);
72 
73         std::string startIconPath;
74         std::string contentStr;
75         std::string endIconPath;
76         std::string labelStr;
77         MenuItemProperties menuItemProps;
78         std::function<void(WeakPtr<NG::FrameNode>)> symbolApply;
79 
80         auto startIcon = menuItemObj->GetProperty("startIcon");
81         auto content = menuItemObj->GetProperty("content");
82         auto endIcon = menuItemObj->GetProperty("endIcon");
83         auto label = menuItemObj->GetProperty("labelInfo");
84         auto symbolStart = menuItemObj->GetProperty("symbolStartIcon");
85         auto symbolEnd = menuItemObj->GetProperty("symbolEndIcon");
86 
87         if (symbolStart->IsObject()) {
88             JSViewAbstract::SetSymbolOptionApply(info, symbolApply, symbolStart);
89             menuItemProps.startApply = symbolApply;
90         } else if (ParseJsMedia(startIcon, startIconPath)) {
91             std::string bundleName;
92             std::string moduleName;
93             GetJsMediaBundleInfo(startIcon, bundleName, moduleName);
94             ImageSourceInfo imageSourceInfo(startIconPath, bundleName, moduleName);
95             menuItemProps.startIcon = imageSourceInfo;
96         }
97 
98         ParseJsString(content, contentStr);
99         menuItemProps.content = contentStr;
100 
101         if (symbolEnd->IsObject()) {
102             JSViewAbstract::SetSymbolOptionApply(info, symbolApply, symbolEnd);
103             menuItemProps.endApply = symbolApply;
104         } else if (ParseJsMedia(endIcon, endIconPath)) {
105             std::string bundleName;
106             std::string moduleName;
107             GetJsMediaBundleInfo(endIcon, bundleName, moduleName);
108             ImageSourceInfo imageSourceInfo(endIconPath, bundleName, moduleName);
109             menuItemProps.endIcon = imageSourceInfo;
110         }
111 
112         if (ParseJsString(label, labelStr)) {
113             menuItemProps.labelInfo = labelStr;
114         }
115 
116         auto builder = menuItemObj->GetProperty("builder");
117         if (!builder.IsEmpty() && builder->IsFunction()) {
118             auto subBuilderFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSFunc>::Cast(builder));
119             CHECK_NULL_VOID(subBuilderFunc);
120             auto targetNode = AceType::WeakClaim(NG::ViewStackProcessor::GetInstance()->GetMainFrameNode());
121             auto subBuildFunc = [execCtx = info.GetExecutionContext(), func = std::move(subBuilderFunc),
122                                     node = targetNode]() {
123                 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
124                 ACE_SCORING_EVENT("MenuItem SubBuilder");
125                 PipelineContext::SetCallBackNode(node);
126                 func->ExecuteJS();
127             };
128             menuItemProps.buildFunc = std::move(subBuildFunc);
129         }
130         MenuItemModel::GetInstance()->Create(menuItemProps);
131     }
132     JSMenuItemTheme::ApplyTheme();
133 }
134 
JSBind(BindingTarget globalObj)135 void JSMenuItem::JSBind(BindingTarget globalObj)
136 {
137     JSClass<JSMenuItem>::Declare("MenuItem");
138     MethodOptions opt = MethodOptions::NONE;
139     JSClass<JSMenuItem>::StaticMethod("create", &JSMenuItem::Create, opt);
140 
141     JSClass<JSMenuItem>::StaticMethod("selected", &JSMenuItem::IsSelected, opt);
142     JSClass<JSMenuItem>::StaticMethod("selectIcon", &JSMenuItem::SelectIcon, opt);
143     JSClass<JSMenuItem>::StaticMethod("onChange", &JSMenuItem::OnChange, opt);
144     JSClass<JSMenuItem>::StaticMethod("contentFont", &JSMenuItem::ContentFont, opt);
145     JSClass<JSMenuItem>::StaticMethod("contentFontColor", &JSMenuItem::ContentFontColor, opt);
146     JSClass<JSMenuItem>::StaticMethod("labelFont", &JSMenuItem::LabelFont, opt);
147     JSClass<JSMenuItem>::StaticMethod("labelFontColor", &JSMenuItem::LabelFontColor, opt);
148     JSClass<JSMenuItem>::StaticMethod("onAttach", &JSInteractableView::JsOnAttach);
149     JSClass<JSMenuItem>::StaticMethod("onAppear", &JSInteractableView::JsOnAppear);
150     JSClass<JSMenuItem>::StaticMethod("onDetach", &JSInteractableView::JsOnDetach);
151     JSClass<JSMenuItem>::StaticMethod("onDisAppear", &JSInteractableView::JsOnDisAppear);
152     JSClass<JSMenuItem>::StaticMethod("onTouch", &JSInteractableView::JsOnTouch);
153     JSClass<JSMenuItem>::InheritAndBind<JSContainerBase>(globalObj);
154 }
155 
ParseIsSelectedObject(const JSCallbackInfo & info,const JSRef<JSVal> & changeEventVal)156 void ParseIsSelectedObject(const JSCallbackInfo& info, const JSRef<JSVal>& changeEventVal)
157 {
158     CHECK_NULL_VOID(changeEventVal->IsFunction());
159 
160     auto jsFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(), JSRef<JSFunc>::Cast(changeEventVal));
161     WeakPtr<NG::FrameNode> targetNode = AceType::WeakClaim(NG::ViewStackProcessor::GetInstance()->GetMainFrameNode());
162     auto onSelected = [execCtx = info.GetExecutionContext(), func = std::move(jsFunc), node = targetNode](
163                           bool selected) {
164         JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
165         ACE_SCORING_EVENT("MenuItem.SelectedChangeEvent");
166         PipelineContext::SetCallBackNode(node);
167         auto newJSVal = JSRef<JSVal>::Make(ToJSValue(selected));
168         func->ExecuteJS(1, &newJSVal);
169     };
170     MenuItemModel::GetInstance()->SetSelectedChangeEvent(std::move(onSelected));
171 }
172 
IsSelected(const JSCallbackInfo & info)173 void JSMenuItem::IsSelected(const JSCallbackInfo& info)
174 {
175     if (info.Length() < 1 || info.Length() > 2) {
176         return;
177     }
178 
179     bool isSelected = false;
180     if (info.Length() > 0 && info[0]->IsBoolean()) {
181         isSelected = info[0]->ToBoolean();
182     }
183     MenuItemModel::GetInstance()->SetSelected(isSelected);
184     if (info.Length() > 1 && info[1]->IsFunction()) {
185         ParseIsSelectedObject(info, info[1]);
186     }
187 }
188 
SelectIcon(const JSCallbackInfo & info)189 void JSMenuItem::SelectIcon(const JSCallbackInfo& info)
190 {
191     bool isShow = false;
192     std::string icon;
193     std::function<void(WeakPtr<NG::FrameNode>)> symbolApply;
194     if (info[0]->IsBoolean()) {
195         isShow = info[0]->ToBoolean();
196     } else if (info[0]->IsString()) {
197         icon = info[0]->ToString();
198         isShow = true;
199     } else if (ParseJsMedia(info[0], icon)) {
200         isShow = true;
201     } else if (info[0]->IsObject()) {
202         isShow = true;
203         JSViewAbstract::SetSymbolOptionApply(info, symbolApply, info[0]);
204     }
205     MenuItemModel::GetInstance()->SetSelectIcon(isShow);
206     MenuItemModel::GetInstance()->SetSelectIconSrc(icon);
207     MenuItemModel::GetInstance()->SetSelectIconSymbol(std::move(symbolApply));
208 }
209 
OnChange(const JSCallbackInfo & info)210 void JSMenuItem::OnChange(const JSCallbackInfo& info)
211 {
212     if (info.Length() < 1 || !info[0]->IsFunction()) {
213         return;
214     }
215     auto jsFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(), JSRef<JSFunc>::Cast(info[0]));
216     WeakPtr<NG::FrameNode> targetNode = AceType::WeakClaim(NG::ViewStackProcessor::GetInstance()->GetMainFrameNode());
217     auto onChange = [execCtx = info.GetExecutionContext(), func = std::move(jsFunc), node = targetNode](bool selected) {
218         JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
219         ACE_SCORING_EVENT("MenuItem.onChange");
220         PipelineContext::SetCallBackNode(node);
221         JSRef<JSVal> params[1];
222         params[0] = JSRef<JSVal>::Make(ToJSValue(selected));
223         func->ExecuteJS(1, params);
224     };
225     MenuItemModel::GetInstance()->SetOnChange(std::move(onChange));
226     info.ReturnSelf();
227 }
228 
ContentFont(const JSCallbackInfo & info)229 void JSMenuItem::ContentFont(const JSCallbackInfo& info)
230 {
231     CalcDimension fontSize;
232     std::string weight;
233     if (!info[0]->IsObject()) {
234         return;
235     } else {
236         JSRef<JSObject> obj = JSRef<JSObject>::Cast(info[0]);
237         JSRef<JSVal> size = obj->GetProperty("size");
238         if (!size->IsNull()) {
239             ParseJsDimensionFp(size, fontSize);
240             if (fontSize.Unit() == DimensionUnit::PERCENT) {
241                 // set zero for abnormal value
242                 fontSize = CalcDimension();
243             }
244         }
245 
246         auto jsWeight = obj->GetProperty("weight");
247         if (!jsWeight->IsNull()) {
248             if (jsWeight->IsNumber()) {
249                 weight = std::to_string(jsWeight->ToNumber<int32_t>());
250             } else {
251                 ParseJsString(jsWeight, weight);
252             }
253         }
254 
255         auto jsStyle = obj->GetProperty("style");
256         if (!jsStyle->IsNull()) {
257             if (jsStyle->IsNumber()) {
258                 MenuItemModel::GetInstance()->SetFontStyle(static_cast<FontStyle>(jsStyle->ToNumber<int32_t>()));
259             } else {
260                 std::string style;
261                 ParseJsString(jsStyle, style);
262                 MenuItemModel::GetInstance()->SetFontStyle(ConvertStrToFontStyle(style));
263             }
264         }
265 
266         auto jsFamily = obj->GetProperty("family");
267         if (!jsFamily->IsNull() && jsFamily->IsString()) {
268             auto familyVal = jsFamily->ToString();
269             auto fontFamilies = ConvertStrToFontFamilies(familyVal);
270             MenuItemModel::GetInstance()->SetFontFamily(fontFamilies);
271         }
272     }
273     MenuItemModel::GetInstance()->SetFontSize(fontSize);
274     MenuItemModel::GetInstance()->SetFontWeight(ConvertStrToFontWeight(weight));
275 }
276 
ContentFontColor(const JSCallbackInfo & info)277 void JSMenuItem::ContentFontColor(const JSCallbackInfo& info)
278 {
279     std::optional<Color> color = std::nullopt;
280     if (info.Length() < 1) {
281         return;
282     } else {
283         Color textColor;
284         if (ParseJsColor(info[0], textColor)) {
285             color = textColor;
286         }
287     }
288     MenuItemModel::GetInstance()->SetFontColor(color);
289 }
290 
LabelFont(const JSCallbackInfo & info)291 void JSMenuItem::LabelFont(const JSCallbackInfo& info)
292 {
293     CalcDimension fontSize;
294     std::string weight;
295     if (!info[0]->IsObject()) {
296         return;
297     } else {
298         JSRef<JSObject> obj = JSRef<JSObject>::Cast(info[0]);
299         JSRef<JSVal> size = obj->GetProperty("size");
300         if (!size->IsNull()) {
301             ParseJsDimensionFp(size, fontSize);
302             if (fontSize.Unit() == DimensionUnit::PERCENT) {
303                 // set zero for abnormal value
304                 fontSize = CalcDimension();
305             }
306         }
307 
308         auto jsWeight = obj->GetProperty("weight");
309         if (!jsWeight->IsNull()) {
310             if (jsWeight->IsNumber()) {
311                 weight = std::to_string(jsWeight->ToNumber<int32_t>());
312             } else {
313                 ParseJsString(jsWeight, weight);
314             }
315         }
316 
317         auto jsStyle = obj->GetProperty("style");
318         if (!jsStyle->IsNull()) {
319             if (jsStyle->IsNumber()) {
320                 MenuItemModel::GetInstance()->SetLabelFontStyle(static_cast<FontStyle>(jsStyle->ToNumber<int32_t>()));
321             } else {
322                 std::string style;
323                 ParseJsString(jsStyle, style);
324                 MenuItemModel::GetInstance()->SetLabelFontStyle(ConvertStrToFontStyle(style));
325             }
326         }
327 
328         auto jsFamily = obj->GetProperty("family");
329         if (!jsFamily->IsNull() && jsFamily->IsString()) {
330             auto familyVal = jsFamily->ToString();
331             auto fontFamilies = ConvertStrToFontFamilies(familyVal);
332             MenuItemModel::GetInstance()->SetLabelFontFamily(fontFamilies);
333         }
334     }
335     MenuItemModel::GetInstance()->SetLabelFontSize(fontSize);
336     MenuItemModel::GetInstance()->SetLabelFontWeight(ConvertStrToFontWeight(weight));
337 }
338 
LabelFontColor(const JSCallbackInfo & info)339 void JSMenuItem::LabelFontColor(const JSCallbackInfo& info)
340 {
341     std::optional<Color> color = std::nullopt;
342     if (info.Length() < 1) {
343         return;
344     } else {
345         Color textColor;
346         if (ParseJsColor(info[0], textColor)) {
347             color = textColor;
348         }
349     }
350     MenuItemModel::GetInstance()->SetLabelFontColor(color);
351 }
352 } // namespace OHOS::Ace::Framework
353