1 /*
2 * Copyright (c) 2021-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 "frameworks/bridge/declarative_frontend/jsview/js_tabs.h"
17
18 #include "base/log/ace_scoring_log.h"
19 #include "bridge/declarative_frontend/jsview/js_tabs_controller.h"
20 #include "bridge/declarative_frontend/jsview/js_view_common_def.h"
21 #include "bridge/declarative_frontend/jsview/models/tabs_model_impl.h"
22 #include "core/components_ng/pattern/tabs/tabs_model_ng.h"
23
24 namespace OHOS::Ace {
25
26 std::unique_ptr<TabsModel> TabsModel::instance_ = nullptr;
27 std::mutex TabsModel::mutex_;
28
GetInstance()29 TabsModel* TabsModel::GetInstance()
30 {
31 if (!instance_) {
32 std::lock_guard<std::mutex> lock(mutex_);
33 if (!instance_) {
34 #ifdef NG_BUILD
35 instance_.reset(new NG::TabsModelNG());
36 #else
37 if (Container::IsCurrentUseNewPipeline()) {
38 instance_.reset(new NG::TabsModelNG());
39 } else {
40 instance_.reset(new Framework::TabsModelImpl());
41 }
42 #endif
43 }
44 }
45 return instance_.get();
46 }
47
48 } // namespace OHOS::Ace
49
50 namespace OHOS::Ace::Framework {
51 namespace {
52 constexpr int32_t SM_COLUMN_NUM = 4;
53 constexpr int32_t MD_COLUMN_NUM = 8;
54 constexpr int32_t LG_COLUMN_NUM = 12;
55 const static int32_t PLATFORM_VERSION_TEN = 10;
56 const std::vector<BarPosition> BAR_POSITIONS = { BarPosition::START, BarPosition::END };
57
TabContentChangeEventToJSValue(const TabContentChangeEvent & eventInfo)58 JSRef<JSVal> TabContentChangeEventToJSValue(const TabContentChangeEvent& eventInfo)
59 {
60 return JSRef<JSVal>::Make(ToJSValue(eventInfo.GetIndex()));
61 }
62
63 } // namespace
64
SetOnChange(const JSCallbackInfo & info)65 void JSTabs::SetOnChange(const JSCallbackInfo& info)
66 {
67 if (!info[0]->IsFunction()) {
68 return;
69 }
70
71 auto changeHandler = AceType::MakeRefPtr<JsEventFunction<TabContentChangeEvent, 1>>(
72 JSRef<JSFunc>::Cast(info[0]), TabContentChangeEventToJSValue);
73 auto onChange = [executionContext = info.GetExecutionContext(), func = std::move(changeHandler)](
74 const BaseEventInfo* info) {
75 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(executionContext);
76 const auto* tabsInfo = TypeInfoHelper::DynamicCast<TabContentChangeEvent>(info);
77 if (!tabsInfo) {
78 LOGE("SetOnChange tabsInfo is nullptr");
79 return;
80 }
81 ACE_SCORING_EVENT("Tabs.onChange");
82 func->Execute(*tabsInfo);
83 };
84 TabsModel::GetInstance()->SetOnChange(std::move(onChange));
85 }
86
SetOnTabBarClick(const JSCallbackInfo & info)87 void JSTabs::SetOnTabBarClick(const JSCallbackInfo& info)
88 {
89 if (!info[0]->IsFunction()) {
90 return;
91 }
92
93 auto changeHandler = AceType::MakeRefPtr<JsEventFunction<TabContentChangeEvent, 1>>(
94 JSRef<JSFunc>::Cast(info[0]), TabContentChangeEventToJSValue);
95 auto onTabBarClick = [executionContext = info.GetExecutionContext(), func = std::move(changeHandler)](
96 const BaseEventInfo* info) {
97 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(executionContext);
98 const auto* tabsInfo = TypeInfoHelper::DynamicCast<TabContentChangeEvent>(info);
99 if (!tabsInfo) {
100 LOGE("SetTabBarClick tabsInfo is nullptr");
101 return;
102 }
103 ACE_SCORING_EVENT("Tabs.onTabBarClick");
104 func->Execute(*tabsInfo);
105 };
106 TabsModel::GetInstance()->SetOnTabBarClick(std::move(onTabBarClick));
107 }
108
ParseTabsIndexObject(const JSCallbackInfo & info,const JSRef<JSVal> & changeEventVal)109 void ParseTabsIndexObject(const JSCallbackInfo& info, const JSRef<JSVal>& changeEventVal)
110 {
111 CHECK_NULL_VOID(changeEventVal->IsFunction());
112
113 auto jsFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(), JSRef<JSFunc>::Cast(changeEventVal));
114 auto onChangeEvent = [executionContext = info.GetExecutionContext(), func = std::move(jsFunc)](
115 const BaseEventInfo* info) {
116 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(executionContext);
117 const auto* tabsInfo = TypeInfoHelper::DynamicCast<TabContentChangeEvent>(info);
118 if (!tabsInfo) {
119 LOGE("ParseTabsIndexObject tabsInfo is nullptr");
120 return;
121 }
122 ACE_SCORING_EVENT("Tabs.onChangeEvent");
123 auto newJSVal = JSRef<JSVal>::Make(ToJSValue(tabsInfo->GetIndex()));
124 func->ExecuteJS(1, &newJSVal);
125 };
126 TabsModel::GetInstance()->SetOnChangeEvent(std::move(onChangeEvent));
127 }
128
Create(const JSCallbackInfo & info)129 void JSTabs::Create(const JSCallbackInfo& info)
130 {
131 BarPosition barPosition = BarPosition::START;
132 RefPtr<TabController> tabController;
133 RefPtr<SwiperController> swiperController;
134 int32_t index = -1;
135 JSRef<JSVal> changeEventVal;
136 if (info[0]->IsObject()) {
137 JSRef<JSObject> obj = JSRef<JSObject>::Cast(info[0]);
138 JSRef<JSVal> val = obj->GetProperty("barPosition");
139 if (val->IsNumber()) {
140 auto barPositionVal = val->ToNumber<int32_t>();
141 if (barPositionVal >= 0 && barPositionVal < static_cast<int32_t>(BAR_POSITIONS.size())) {
142 barPosition = BAR_POSITIONS[barPositionVal];
143 }
144 }
145 JSRef<JSVal> controller = obj->GetProperty("controller");
146 if (controller->IsObject()) {
147 auto* jsTabsController = JSRef<JSObject>::Cast(controller)->Unwrap<JSTabsController>();
148 if (jsTabsController) {
149 tabController = jsTabsController->GetController();
150 swiperController = jsTabsController->GetSwiperController();
151 }
152 }
153 JSRef<JSVal> indexVal = obj->GetProperty("index");
154 if (indexVal->IsNumber()) {
155 index = indexVal->ToNumber<int32_t>();
156 index = index < 0 ? 0 : index;
157 if (!tabController) {
158 tabController = JSTabsController::CreateController();
159 }
160 #ifndef NG_BUILD
161 tabController->SetInitialIndex(index);
162 #endif
163 } else if (indexVal->IsObject()) {
164 JSRef<JSObject> indexObj = JSRef<JSObject>::Cast(indexVal);
165 auto indexValueProperty = indexObj->GetProperty("value");
166 if (indexValueProperty->IsNumber()) {
167 index = indexValueProperty->ToNumber<int32_t>();
168 index = index < 0 ? 0 : index;
169 }
170 changeEventVal = indexObj->GetProperty("changeEvent");
171 }
172 }
173
174 TabsModel::GetInstance()->Create(barPosition, index, tabController, swiperController);
175 if (!changeEventVal->IsUndefined() && changeEventVal->IsFunction()) {
176 ParseTabsIndexObject(info, changeEventVal);
177 }
178 }
179
Pop()180 void JSTabs::Pop()
181 {
182 TabsModel::GetInstance()->Pop();
183 }
184
SetBarPosition(const JSCallbackInfo & info)185 void JSTabs::SetBarPosition(const JSCallbackInfo& info)
186 {
187 BarPosition barVal = BarPosition::START;
188 if (info.Length() > 0 && info[0]->IsNumber()) {
189 auto barPositionVal = info[0]->ToNumber<int32_t>();
190 if (barPositionVal >= 0 && barPositionVal < static_cast<int32_t>(BAR_POSITIONS.size())) {
191 barVal = BAR_POSITIONS[barPositionVal];
192 }
193 }
194
195 TabsModel::GetInstance()->SetTabBarPosition(barVal);
196 }
197
SetVertical(const std::string & value)198 void JSTabs::SetVertical(const std::string& value)
199 {
200 TabsModel::GetInstance()->SetIsVertical(StringToBool(value));
201 }
202
SetScrollable(const std::string & value)203 void JSTabs::SetScrollable(const std::string& value)
204 {
205 if (value == "undefined") {
206 TabsModel::GetInstance()->SetScrollable(true);
207 return;
208 }
209 TabsModel::GetInstance()->SetScrollable(StringToBool(value));
210 }
211
SetBarMode(const JSCallbackInfo & info)212 void JSTabs::SetBarMode(const JSCallbackInfo& info)
213 {
214 TabBarMode barMode = TabBarMode::FIXED;
215 if (info.Length() < 1) {
216 TabsModel::GetInstance()->SetTabBarMode(barMode);
217 return;
218 }
219 if (info[0]->IsString()) {
220 barMode = ConvertStrToTabBarMode(info[0]->ToString());
221 }
222 if (barMode == TabBarMode::SCROLLABLE) {
223 if (info.Length() > 1 && info[1]->IsObject()) {
224 SetScrollableBarModeOptions(info[1]);
225 } else {
226 ScrollableBarModeOptions option;
227 TabsModel::GetInstance()->SetScrollableBarModeOptions(option);
228 }
229 }
230 TabsModel::GetInstance()->SetTabBarMode(barMode);
231 }
232
SetBarWidth(const JSCallbackInfo & info)233 void JSTabs::SetBarWidth(const JSCallbackInfo& info)
234 {
235 if (info.Length() < 1) {
236 LOGE("The arg is wrong, it is supposed to have atleast 1 arguments");
237 return;
238 }
239
240 CalcDimension width = Dimension(-1.0, DimensionUnit::VP);
241 if (PipelineBase::GetCurrentContext() &&
242 PipelineBase::GetCurrentContext()->GetMinPlatformVersion() >= PLATFORM_VERSION_TEN) {
243 if (!ParseJsDimensionVpNG(info[0], width)) {
244 width = Dimension(-1.0, DimensionUnit::VP);
245 TabsModel::GetInstance()->SetTabBarWidth(width);
246 return;
247 }
248 } else if (!ParseJsDimensionVp(info[0], width)) {
249 LOGE("The arg is wrong, fail to parse dimension");
250 }
251
252 TabsModel::GetInstance()->SetTabBarWidth(width);
253 }
254
SetBarHeight(const JSCallbackInfo & info)255 void JSTabs::SetBarHeight(const JSCallbackInfo& info)
256 {
257 if (info.Length() < 1) {
258 LOGE("The arg is wrong, it is supposed to have atleast 1 arguments");
259 return;
260 }
261 CalcDimension height = Dimension(-1.0, DimensionUnit::VP);
262 bool adaptiveHeight = false;
263 if (info[0]->IsString() && info[0]->ToString() == "auto") {
264 adaptiveHeight = true;
265 } else {
266 if (PipelineBase::GetCurrentContext() &&
267 PipelineBase::GetCurrentContext()->GetMinPlatformVersion() >= PLATFORM_VERSION_TEN) {
268 if (!ParseJsDimensionVpNG(info[0], height)) {
269 height = Dimension(-1.0, DimensionUnit::VP);
270 LOGD("The arg is wrong, fail to parse dimension");
271 }
272 } else if (!ParseJsDimensionVp(info[0], height)) {
273 LOGD("The arg is wrong, fail to parse dimension");
274 }
275 }
276 TabsModel::GetInstance()->SetBarAdaptiveHeight(adaptiveHeight);
277 TabsModel::GetInstance()->SetTabBarHeight(height);
278 }
279
SetIndex(int32_t index)280 void JSTabs::SetIndex(int32_t index)
281 {
282 TabsModel::GetInstance()->SetIndex(index);
283 }
284
SetAnimationDuration(float value)285 void JSTabs::SetAnimationDuration(float value)
286 {
287 if (std::isnan(value)) {
288 LOGI("The arg is nan, use default value");
289 auto pipelineContext = PipelineContext::GetCurrentContext();
290 CHECK_NULL_VOID(pipelineContext);
291 auto tabTheme = pipelineContext->GetTheme<TabTheme>();
292 CHECK_NULL_VOID(tabTheme);
293 TabsModel::GetInstance()->SetAnimationDuration(static_cast<float>(tabTheme->GetTabContentAnimationDuration()));
294 return;
295 }
296 TabsModel::GetInstance()->SetAnimationDuration(value);
297 }
298
SetFadingEdge(const JSCallbackInfo & info)299 void JSTabs::SetFadingEdge(const JSCallbackInfo& info)
300 {
301 bool fadingEdge = true;
302 if (info.Length() < 1) {
303 LOGW("The arg is wrong, it is supposed to have at least 1 arguments");
304 } else if (!ParseJsBool(info[0], fadingEdge)) {
305 LOGW("The arg is wrong, fail to parse bool");
306 }
307 TabsModel::GetInstance()->SetFadingEdge(fadingEdge);
308 }
309
SetBarOverlap(const JSCallbackInfo & info)310 void JSTabs::SetBarOverlap(const JSCallbackInfo& info)
311 {
312 bool barOverlap = false;
313 if (info.Length() < 1) {
314 LOGW("The arg is wrong, it is supposed to have at least 1 arguments");
315 } else if (!ParseJsBool(info[0], barOverlap)) {
316 LOGW("The arg is wrong, fail to parse bool");
317 }
318 TabsModel::GetInstance()->SetBarOverlap(barOverlap);
319 }
320
SetBarBackgroundColor(const JSCallbackInfo & info)321 void JSTabs::SetBarBackgroundColor(const JSCallbackInfo& info)
322 {
323 Color backgroundColor = Color::BLACK.BlendOpacity(0.0f);
324 if (info.Length() < 1) {
325 LOGD("Invalid parameters. Use default parameters instead.");
326 } else if (!ConvertFromJSValue(info[0], backgroundColor)) {
327 LOGD("Invalid parameters. Use default parameters instead.");
328 }
329 TabsModel::GetInstance()->SetBarBackgroundColor(backgroundColor);
330 }
331
SetDivider(const JSCallbackInfo & info)332 void JSTabs::SetDivider(const JSCallbackInfo& info)
333 {
334 TabsItemDivider divider;
335 CalcDimension dividerStrokeWidth;
336 CalcDimension dividerStartMargin;
337 CalcDimension dividerEndMargin;
338 RefPtr<TabTheme> tabTheme = GetTheme<TabTheme>();
339 CHECK_NULL_VOID(tabTheme);
340
341 if (info.Length() < 1) {
342 LOGW("Invalid params");
343 } else {
344 JSRef<JSObject> obj = JSRef<JSObject>::Cast(info[0]);
345 if (info[0]->IsNull()) {
346 divider.isNull = true;
347 } else {
348 if (!info[0]->IsObject() || !ParseJsDimensionVp(obj->GetProperty("strokeWidth"), dividerStrokeWidth) ||
349 dividerStrokeWidth.Value() < 0.0f || dividerStrokeWidth.Unit() == DimensionUnit::PERCENT) {
350 divider.strokeWidth.Reset();
351 } else {
352 divider.strokeWidth = dividerStrokeWidth;
353 }
354 if (!info[0]->IsObject() || !ConvertFromJSValue(obj->GetProperty("color"), divider.color)) {
355 divider.color = tabTheme->GetDividerColor();
356 }
357 if (!info[0]->IsObject() || !ParseJsDimensionVp(obj->GetProperty("startMargin"), dividerStartMargin) ||
358 dividerStartMargin.Value() < 0.0f || dividerStartMargin.Unit() == DimensionUnit::PERCENT) {
359 divider.startMargin.Reset();
360 } else {
361 divider.startMargin = dividerStartMargin;
362 }
363 if (!info[0]->IsObject() || !ParseJsDimensionVp(obj->GetProperty("endMargin"), dividerEndMargin) ||
364 dividerEndMargin.Value() < 0.0f || dividerEndMargin.Unit() == DimensionUnit::PERCENT) {
365 divider.endMargin.Reset();
366 } else {
367 divider.endMargin = dividerEndMargin;
368 }
369 }
370 }
371 TabsModel::GetInstance()->SetDivider(divider);
372 }
373
SetClip(const JSCallbackInfo & info)374 void JSTabs::SetClip(const JSCallbackInfo& info)
375 {
376 if (info[0]->IsObject() || !Container::IsCurrentUseNewPipeline()) {
377 JSViewAbstract::JsClip(info);
378 return;
379 }
380 if (info[0]->IsBoolean()) {
381 TabsModel::GetInstance()->SetClipEdge(info[0]->ToBoolean());
382 }
383 }
384
SetScrollableBarModeOptions(const JSRef<JSVal> & info)385 void JSTabs::SetScrollableBarModeOptions(const JSRef<JSVal>& info)
386 {
387 ScrollableBarModeOptions option;
388 auto optionParam = JSRef<JSObject>::Cast(info);
389 CalcDimension margin = Dimension(0.0, DimensionUnit::VP);
390 if (!ParseJsDimensionVp(optionParam->GetProperty("margin"), margin) || Negative(margin.Value()) ||
391 margin.Unit() == DimensionUnit::PERCENT) {
392 option.margin = 0.0_vp;
393 } else {
394 option.margin = margin;
395 }
396
397 auto nonScrollableLayoutStyle = optionParam->GetProperty("nonScrollableLayoutStyle");
398 int32_t layoutStyle;
399 if (!ConvertFromJSValue(nonScrollableLayoutStyle, layoutStyle)) {
400 option.nonScrollableLayoutStyle = LayoutStyle::ALWAYS_CENTER;
401 } else {
402 option.nonScrollableLayoutStyle = (static_cast<LayoutStyle>(layoutStyle));
403 }
404 TabsModel::GetInstance()->SetScrollableBarModeOptions(option);
405 }
406
SetBarGridAlign(const JSCallbackInfo & info)407 void JSTabs::SetBarGridAlign(const JSCallbackInfo& info)
408 {
409 BarGridColumnOptions columnOption;
410 if (info.Length() < 1) {
411 LOGD("Invalid parameters. Use default parameters instead.");
412 } else if (info[0]->IsObject()) {
413 auto gridParam = JSRef<JSObject>::Cast(info[0]);
414 auto sm = gridParam->GetProperty("sm");
415 if (sm->IsNumber() && sm->ToNumber<int32_t>() >= 0 && sm->ToNumber<int32_t>() <= SM_COLUMN_NUM &&
416 sm->ToNumber<int32_t>() % 2 == 0) {
417 columnOption.sm = sm->ToNumber<int32_t>();
418 }
419 auto md = gridParam->GetProperty("md");
420 if (md->IsNumber() && md->ToNumber<int32_t>() >= 0 && md->ToNumber<int32_t>() <= MD_COLUMN_NUM &&
421 md->ToNumber<int32_t>() % 2 == 0) {
422 columnOption.md = md->ToNumber<int32_t>();
423 }
424 auto lg = gridParam->GetProperty("lg");
425 if (lg->IsNumber() && lg->ToNumber<int32_t>() >= 0 && lg->ToNumber<int32_t>() <= LG_COLUMN_NUM &&
426 lg->ToNumber<int32_t>() % 2 == 0) {
427 columnOption.lg = lg->ToNumber<int32_t>();
428 }
429 CalcDimension columnGutter;
430 if (ParseJsDimensionVp(gridParam->GetProperty("gutter"), columnGutter) && NonNegative(columnGutter.Value()) &&
431 columnGutter.Unit() != DimensionUnit::PERCENT) {
432 columnOption.gutter = columnGutter;
433 }
434 CalcDimension columnMargin;
435 if (ParseJsDimensionVp(gridParam->GetProperty("margin"), columnMargin) && NonNegative(columnMargin.Value()) &&
436 columnMargin.Unit() != DimensionUnit::PERCENT) {
437 columnOption.margin = columnMargin;
438 }
439 }
440 TabsModel::GetInstance()->SetBarGridAlign(columnOption);
441 }
442
JSBind(BindingTarget globalObj)443 void JSTabs::JSBind(BindingTarget globalObj)
444 {
445 JSClass<JSTabs>::Declare("Tabs");
446 JSClass<JSTabs>::StaticMethod("create", &JSTabs::Create);
447 JSClass<JSTabs>::StaticMethod("pop", &JSTabs::Pop);
448 JSClass<JSTabs>::StaticMethod("vertical", &JSTabs::SetVertical);
449 JSClass<JSTabs>::StaticMethod("barPosition", &JSTabs::SetBarPosition);
450 JSClass<JSTabs>::StaticMethod("scrollable", &JSTabs::SetScrollable);
451 JSClass<JSTabs>::StaticMethod("barMode", &JSTabs::SetBarMode);
452 JSClass<JSTabs>::StaticMethod("barWidth", &JSTabs::SetBarWidth);
453 JSClass<JSTabs>::StaticMethod("barHeight", &JSTabs::SetBarHeight);
454 JSClass<JSTabs>::StaticMethod("index", &JSTabs::SetIndex);
455 JSClass<JSTabs>::StaticMethod("animationDuration", &JSTabs::SetAnimationDuration);
456 JSClass<JSTabs>::StaticMethod("divider", &JSTabs::SetDivider);
457 JSClass<JSTabs>::StaticMethod("onChange", &JSTabs::SetOnChange);
458 JSClass<JSTabs>::StaticMethod("onTabBarClick", &JSTabs::SetOnTabBarClick);
459 JSClass<JSTabs>::StaticMethod("onAppear", &JSInteractableView::JsOnAppear);
460 JSClass<JSTabs>::StaticMethod("onDisAppear", &JSInteractableView::JsOnDisAppear);
461 JSClass<JSTabs>::StaticMethod("onTouch", &JSInteractableView::JsOnTouch);
462 JSClass<JSTabs>::StaticMethod("onHover", &JSInteractableView::JsOnHover);
463 JSClass<JSTabs>::StaticMethod("onKeyEvent", &JSInteractableView::JsOnKey);
464 JSClass<JSTabs>::StaticMethod("onDeleteEvent", &JSInteractableView::JsOnDelete);
465 JSClass<JSTabs>::StaticMethod("onClick", &JSInteractableView::JsOnClick);
466 JSClass<JSTabs>::StaticMethod("remoteMessage", &JSInteractableView::JsCommonRemoteMessage);
467 JSClass<JSTabs>::StaticMethod("fadingEdge", &JSTabs::SetFadingEdge);
468 JSClass<JSTabs>::StaticMethod("barOverlap", &JSTabs::SetBarOverlap);
469 JSClass<JSTabs>::StaticMethod("barBackgroundColor", &JSTabs::SetBarBackgroundColor);
470 JSClass<JSTabs>::StaticMethod("clip", &JSTabs::SetClip);
471 JSClass<JSTabs>::StaticMethod("barGridAlign", &JSTabs::SetBarGridAlign);
472
473 JSClass<JSTabs>::InheritAndBind<JSContainerBase>(globalObj);
474 }
475
476 } // namespace OHOS::Ace::Framework
477