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.h"
17 
18 #include "bridge/declarative_frontend/jsview/js_view_common_def.h"
19 #include "bridge/declarative_frontend/jsview/models/menu_model_impl.h"
20 #include "core/components_ng/layout/layout_property.h"
21 #include "core/components_ng/pattern/menu/menu_model.h"
22 #include "core/components_ng/pattern/menu/menu_model_ng.h"
23 #include "core/components_ng/property/measure_property.h"
24 #include "bridge/declarative_frontend/ark_theme/theme_apply/js_menu_theme.h"
25 
26 namespace OHOS::Ace {
27 std::unique_ptr<MenuModel> MenuModel::instance_ = nullptr;
28 std::mutex MenuModel::mutex_;
29 
GetInstance()30 MenuModel* MenuModel::GetInstance()
31 {
32     if (!instance_) {
33         std::lock_guard<std::mutex> lock(mutex_);
34         if (!instance_) {
35 #ifdef NG_BUILD
36             instance_.reset(new NG::MenuModelNG());
37 #else
38             if (Container::IsCurrentUseNewPipeline()) {
39                 instance_.reset(new NG::MenuModelNG());
40             } else {
41                 instance_.reset(new Framework::MenuModelImpl());
42             }
43 #endif
44         }
45     }
46     return instance_.get();
47 }
48 } // namespace OHOS::Ace
49 
50 namespace OHOS::Ace::Framework {
Create(const JSCallbackInfo &)51 void JSMenu::Create(const JSCallbackInfo& /* info */)
52 {
53     MenuModel::GetInstance()->Create();
54     JSMenuTheme::ApplyTheme();
55 }
56 
FontSize(const JSCallbackInfo & info)57 void JSMenu::FontSize(const JSCallbackInfo& info)
58 {
59     if (info.Length() < 1) {
60         return;
61     }
62     CalcDimension fontSize;
63     if (!ParseJsDimensionFp(info[0], fontSize)) {
64         return;
65     }
66     MenuModel::GetInstance()->SetFontSize(fontSize);
67 }
68 
Font(const JSCallbackInfo & info)69 void JSMenu::Font(const JSCallbackInfo& info)
70 {
71     CalcDimension fontSize;
72     std::string weight;
73     if (!info[0]->IsObject()) {
74         if (AceApplicationInfo::GetInstance().GreatOrEqualTargetAPIVersion(PlatformVersion::VERSION_TWELVE)) {
75             MenuModel::GetInstance()->SetFontSize(CalcDimension());
76             MenuModel::GetInstance()->SetFontWeight(FontWeight::NORMAL);
77             MenuModel::GetInstance()->SetFontStyle(FontStyle::NORMAL);
78             MenuModel::GetInstance()->ResetFontFamily();
79         }
80         return;
81     } else {
82         JSRef<JSObject> obj = JSRef<JSObject>::Cast(info[0]);
83         JSRef<JSVal> size = obj->GetProperty("size");
84         if (!size->IsNull()) {
85             ParseJsDimensionFp(size, fontSize);
86             if (fontSize.Unit() == DimensionUnit::PERCENT) {
87                 // set zero for abnormal value
88                 fontSize = CalcDimension();
89             }
90         }
91         auto jsWeight = obj->GetProperty("weight");
92         if (!jsWeight->IsNull()) {
93             if (jsWeight->IsNumber()) {
94                 weight = std::to_string(jsWeight->ToNumber<int32_t>());
95             } else {
96                 ParseJsString(jsWeight, weight);
97             }
98         }
99         auto jsStyle = obj->GetProperty("style");
100         if (!jsStyle->IsNull()) {
101             if (jsStyle->IsNumber()) {
102                 MenuModel::GetInstance()->SetFontStyle(static_cast<FontStyle>(jsStyle->ToNumber<int32_t>()));
103             } else {
104                 std::string style;
105                 ParseJsString(jsStyle, style);
106                 MenuModel::GetInstance()->SetFontStyle(ConvertStrToFontStyle(style));
107             }
108         }
109         auto jsFamily = obj->GetProperty("family");
110         if (!jsFamily->IsNull() && jsFamily->IsString()) {
111             auto familyVal = jsFamily->ToString();
112             auto fontFamilies = ConvertStrToFontFamilies(familyVal);
113             MenuModel::GetInstance()->SetFontFamily(fontFamilies);
114         }
115     }
116     MenuModel::GetInstance()->SetFontSize(fontSize);
117     MenuModel::GetInstance()->SetFontWeight(ConvertStrToFontWeight(weight));
118 }
119 
FontColor(const JSCallbackInfo & info)120 void JSMenu::FontColor(const JSCallbackInfo& info)
121 {
122     std::optional<Color> color = std::nullopt;
123     if (info.Length() < 1) {
124         return;
125     } else {
126         Color textColor;
127         if (ParseJsColor(info[0], textColor)) {
128             color = textColor;
129         }
130     }
131     MenuModel::GetInstance()->SetFontColor(color);
132 }
133 
SetWidth(const JSCallbackInfo & info)134 void JSMenu::SetWidth(const JSCallbackInfo& info)
135 {
136     if (info.Length() < 1) {
137         return;
138     }
139     CalcDimension width;
140     ParseJsDimensionVp(info[0], width);
141     MenuModel::GetInstance()->SetWidth(width);
142 }
143 
HandleDifferentRadius(const JSRef<JSVal> & args)144 void JSMenu::HandleDifferentRadius(const JSRef<JSVal>& args)
145 {
146     std::optional<CalcDimension> radiusTopLeft;
147     std::optional<CalcDimension> radiusTopRight;
148     std::optional<CalcDimension> radiusBottomLeft;
149     std::optional<CalcDimension> radiusBottomRight;
150     if (args->IsObject()) {
151         JSRef<JSObject> object = JSRef<JSObject>::Cast(args);
152         CalcDimension topLeft;
153         if (ParseJsDimensionVp(object->GetProperty("topLeft"), topLeft)) {
154             radiusTopLeft = topLeft;
155         }
156         CalcDimension topRight;
157         if (ParseJsDimensionVp(object->GetProperty("topRight"), topRight)) {
158             radiusTopRight = topRight;
159         }
160         CalcDimension bottomLeft;
161         if (ParseJsDimensionVp(object->GetProperty("bottomLeft"), bottomLeft)) {
162             radiusBottomLeft = bottomLeft;
163         }
164         CalcDimension bottomRight;
165         if (ParseJsDimensionVp(object->GetProperty("bottomRight"), bottomRight)) {
166             radiusBottomRight = bottomRight;
167         }
168         if (!radiusTopLeft.has_value() && !radiusTopRight.has_value() && !radiusBottomLeft.has_value() &&
169             !radiusBottomRight.has_value()) {
170             return;
171         }
172         MenuModel::GetInstance()->SetBorderRadius(radiusTopLeft, radiusTopRight, radiusBottomLeft, radiusBottomRight);
173     }
174 }
175 
SetRadius(const JSCallbackInfo & info)176 void JSMenu::SetRadius(const JSCallbackInfo& info)
177 {
178     if (info.Length() < 1) {
179         return;
180     }
181     CalcDimension radius;
182     if (info[0]->IsObject()) {
183         HandleDifferentRadius(info[0]);
184     } else {
185         if (!ParseJsDimensionVpNG(info[0], radius)) {
186             MenuModel::GetInstance()->ResetBorderRadius();
187             return;
188         }
189         if (LessNotEqual(radius.Value(), 0.0)) {
190             MenuModel::GetInstance()->ResetBorderRadius();
191             return;
192         }
193         MenuModel::GetInstance()->SetBorderRadius(radius);
194     }
195 }
196 
SetExpandingMode(const JSCallbackInfo & info)197 void JSMenu::SetExpandingMode(const JSCallbackInfo& info)
198 {
199     if (info.Length() < 1 || info[0]->IsNull() || !info[0]->IsNumber()) {
200         return;
201     }
202 
203     auto mode = static_cast<SubMenuExpandingMode>(info[0]->ToNumber<int32_t>());
204     auto expandingMode =
205         mode == SubMenuExpandingMode::EMBEDDED
206             ? NG::SubMenuExpandingMode::EMBEDDED
207             : mode == SubMenuExpandingMode::STACK
208                 ? NG::SubMenuExpandingMode::STACK
209                 : NG::SubMenuExpandingMode::SIDE;
210 
211     MenuModel::GetInstance()->SetExpandingMode(expandingMode);
212 }
213 
SetItemGroupDivider(const JSCallbackInfo & args)214 void JSMenu::SetItemGroupDivider(const JSCallbackInfo& args)
215 {
216     auto divider = V2::ItemDivider{
217         .strokeWidth = Dimension(0.0f, DimensionUnit::INVALID),
218         .color = Color::FOREGROUND,
219     };
220     if (args.Length() >= 1 && args[0]->IsObject()) {
221         JSRef<JSObject> obj = JSRef<JSObject>::Cast(args[0]);
222         CalcDimension value;
223         if (!ParseLengthMetricsToPositiveDimension(obj->GetProperty("strokeWidth"), value)) {
224             value.Reset();
225             value.SetUnit(DimensionUnit::INVALID);
226         }
227         if (value.IsNegative() || value.Unit() < DimensionUnit::PX || value.Unit() > DimensionUnit::LPX) {
228             value.Reset();
229             value.SetUnit(DimensionUnit::INVALID);
230         }
231         divider.strokeWidth = value;
232         if (!ParseLengthMetricsToPositiveDimension(obj->GetProperty("startMargin"), value)) {
233             value.Reset();
234             value.SetUnit(DimensionUnit::INVALID);
235         }
236         if (value.IsNegative() || value.Unit() < DimensionUnit::PX || value.Unit() > DimensionUnit::LPX) {
237             value.Reset();
238             value.SetUnit(DimensionUnit::INVALID);
239         }
240         divider.startMargin = value;
241         if (!ParseLengthMetricsToPositiveDimension(obj->GetProperty("endMargin"), value)) {
242             value.Reset();
243             value.SetUnit(DimensionUnit::INVALID);
244         }
245         if (value.IsNegative() || value.Unit() < DimensionUnit::PX || value.Unit() > DimensionUnit::LPX) {
246             value.Reset();
247             value.SetUnit(DimensionUnit::INVALID);
248         }
249         divider.endMargin = value;
250         if (!ConvertFromJSValue(obj->GetProperty("color"), divider.color)) {
251             divider.color = Color::FOREGROUND;
252         }
253     }
254     MenuModel::GetInstance()->SetItemGroupDivider(divider);
255     args.ReturnSelf();
256 }
257 
SetItemDivider(const JSCallbackInfo & args)258 void JSMenu::SetItemDivider(const JSCallbackInfo& args)
259 {
260     V2::ItemDivider divider;
261     if (args.Length() >= 1 && args[0]->IsObject()) {
262         JSRef<JSObject> obj = JSRef<JSObject>::Cast(args[0]);
263         CalcDimension value;
264         if (!ParseLengthMetricsToPositiveDimension(obj->GetProperty("strokeWidth"), value)) {
265             value.Reset();
266         }
267         if (value.IsNegative()) {
268             value.Reset();
269         }
270         divider.strokeWidth = value;
271         if (!ParseLengthMetricsToPositiveDimension(obj->GetProperty("startMargin"), value)) {
272             value.Reset();
273         }
274         if (value.IsNegative()) {
275             value.Reset();
276         }
277         divider.startMargin = value;
278         if (!ParseLengthMetricsToPositiveDimension(obj->GetProperty("endMargin"), value)) {
279             value.Reset();
280         }
281         if (value.IsNegative()) {
282             value.Reset();
283         }
284         divider.endMargin = value;
285 
286         if (!ConvertFromJSValue(obj->GetProperty("color"), divider.color)) {
287             divider.color = Color::TRANSPARENT;
288         }
289     }
290     MenuModel::GetInstance()->SetItemDivider(divider);
291     args.ReturnSelf();
292 }
293 
JSBind(BindingTarget globalObj)294 void JSMenu::JSBind(BindingTarget globalObj)
295 {
296     JSClass<JSMenu>::Declare("Menu");
297     MethodOptions opt = MethodOptions::NONE;
298     JSClass<JSMenu>::StaticMethod("create", &JSMenu::Create, opt);
299     JSClass<JSMenu>::StaticMethod("fontSize", &JSMenu::FontSize, opt);
300     JSClass<JSMenu>::StaticMethod("font", &JSMenu::Font, opt);
301     JSClass<JSMenu>::StaticMethod("fontColor", &JSMenu::FontColor, opt);
302     JSClass<JSMenu>::StaticMethod("width", &JSMenu::SetWidth, opt);
303     JSClass<JSMenu>::StaticMethod("radius", &JSMenu::SetRadius, opt);
304     JSClass<JSMenu>::StaticMethod("subMenuExpandingMode", &JSMenu::SetExpandingMode);
305     JSClass<JSMenu>::StaticMethod("menuItemDivider", &JSMenu::SetItemDivider);
306     JSClass<JSMenu>::StaticMethod("menuItemGroupDivider", &JSMenu::SetItemGroupDivider);
307     JSClass<JSMenu>::StaticMethod("onAppear", &JSInteractableView::JsOnAppear);
308     JSClass<JSMenu>::StaticMethod("onDisAppear", &JSInteractableView::JsOnDisAppear);
309     JSClass<JSMenu>::StaticMethod("onAttach", &JSInteractableView::JsOnAttach);
310     JSClass<JSMenu>::StaticMethod("onDetach", &JSInteractableView::JsOnDetach);
311     JSClass<JSMenu>::StaticMethod("onTouch", &JSInteractableView::JsOnTouch);
312     JSClass<JSMenu>::InheritAndBind<JSViewAbstract>(globalObj);
313 }
314 } // namespace OHOS::Ace::Framework
315