1 /*
2 * Copyright (c) 2021-2022 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_radio.h"
17
18 #include "interfaces/inner_api/ui_session/ui_session_manager.h"
19
20 #include "base/log/ace_scoring_log.h"
21 #include "bridge/declarative_frontend/ark_theme/theme_apply/js_radio_theme.h"
22 #include "bridge/declarative_frontend/engine/jsi/js_ui_index.h"
23 #include "bridge/declarative_frontend/jsview/js_interactable_view.h"
24 #include "bridge/declarative_frontend/jsview/js_view_common_def.h"
25 #include "bridge/declarative_frontend/jsview/models/radio_model_impl.h"
26 #include "core/components/checkable/checkable_theme.h"
27 #include "core/components_ng/base/view_abstract.h"
28 #include "core/components_ng/base/view_stack_model.h"
29 #include "core/components_ng/base/view_stack_processor.h"
30 #include "core/components_ng/pattern/radio/radio_model_ng.h"
31 #include "core/components_ng/pattern/radio/radio_pattern.h"
32
33 namespace OHOS::Ace {
34
35 enum class RadioIndicatorType {
36 TICK = 0,
37 DOT,
38 CUSTOM,
39 };
40
GetInstance()41 RadioModel* RadioModel::GetInstance()
42 {
43 #ifdef NG_BUILD
44 static NG::RadioModelNG instance;
45 return &instance;
46 #else
47 if (Container::IsCurrentUseNewPipeline()) {
48 static NG::RadioModelNG instance;
49 return &instance;
50 } else {
51 static Framework::RadioModelImpl instance;
52 return &instance;
53 }
54 #endif
55 }
56
57 } // namespace OHOS::Ace
58 namespace OHOS::Ace::Framework {
Create(const JSCallbackInfo & info)59 void JSRadio::Create(const JSCallbackInfo& info)
60 {
61 if (info.Length() < 1) {
62 return;
63 }
64
65 std::optional<std::string> value;
66 std::optional<std::string> group;
67 std::optional<int32_t> indicator;
68 std::function<void()> customBuilderFunc = nullptr;
69 if ((info.Length() >= 1) && info[0]->IsObject()) {
70 auto paramObject = JSRef<JSObject>::Cast(info[0]);
71 auto valueTemp = paramObject->GetProperty("value");
72 auto groupTemp = paramObject->GetProperty("group");
73 auto indicatorTemp = paramObject->GetProperty("indicatorType");
74 auto builderObject = paramObject->GetProperty("indicatorBuilder");
75 if (valueTemp->IsString()) {
76 value = valueTemp->ToString();
77 } else {
78 value = "";
79 }
80 if (groupTemp->IsString()) {
81 group = groupTemp->ToString();
82 } else {
83 group = "";
84 }
85 indicator = indicatorTemp->ToNumber<int32_t>();
86 ParseIndicator(info, indicator, customBuilderFunc, builderObject);
87 }
88 RadioModel::GetInstance()->Create(value, group, indicator);
89 RadioModel::GetInstance()->SetBuilder(std::move(customBuilderFunc));
90 JSRadioTheme::ApplyTheme();
91 }
92
ParseIndicator(const JSCallbackInfo & info,std::optional<int32_t> & indicator,std::function<void ()> & customBuilderFunc,JSRef<JSVal> & builderObject)93 void JSRadio::ParseIndicator(const JSCallbackInfo& info, std::optional<int32_t>& indicator,
94 std::function<void()>& customBuilderFunc, JSRef<JSVal>& builderObject)
95 {
96 if (indicator.value_or(static_cast<int32_t>(RadioIndicatorType::TICK)) ==
97 static_cast<int32_t>(RadioIndicatorType::CUSTOM)) {
98 if (builderObject->IsFunction()) {
99 auto builderFunc = AceType::MakeRefPtr<JsFunction>(info.This(), JSRef<JSFunc>::Cast(builderObject));
100 CHECK_NULL_VOID(builderFunc);
101 auto targetNode = AceType::WeakClaim(NG::ViewStackProcessor::GetInstance()->GetMainFrameNode());
102 customBuilderFunc = [execCtx = info.GetExecutionContext(), func = std::move(builderFunc),
103 node = targetNode]() {
104 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
105 ACE_SCORING_EVENT("Radio.builder");
106 PipelineContext::SetCallBackNode(node);
107 func->Execute();
108 };
109 } else {
110 indicator = static_cast<int32_t>(RadioIndicatorType::TICK);
111 }
112 }
113 }
114
JSBind(BindingTarget globalObj)115 void JSRadio::JSBind(BindingTarget globalObj)
116 {
117 JSClass<JSRadio>::Declare("Radio");
118
119 JSClass<JSRadio>::StaticMethod("create", &JSRadio::Create);
120 JSClass<JSRadio>::StaticMethod("checked", &JSRadio::Checked);
121 JSClass<JSRadio>::StaticMethod("size", &JSRadio::JsSize);
122 JSClass<JSRadio>::StaticMethod("padding", &JSRadio::JsPadding);
123 JSClass<JSRadio>::StaticMethod("margin", &JSRadio::JsMargin);
124 JSClass<JSRadio>::StaticMethod("radioStyle", &JSRadio::JsRadioStyle);
125 JSClass<JSRadio>::StaticMethod("responseRegion", &JSRadio::JsResponseRegion);
126 JSClass<JSRadio>::StaticMethod("hoverEffect", &JSRadio::JsHoverEffect);
127 JSClass<JSRadio>::StaticMethod("onChange", &JSRadio::OnChange);
128 JSClass<JSRadio>::StaticMethod("onClick", &JSRadio::JsOnClick);
129 JSClass<JSRadio>::StaticMethod("onTouch", &JSInteractableView::JsOnTouch);
130 JSClass<JSRadio>::StaticMethod("onKeyEvent", &JSInteractableView::JsOnKey);
131 JSClass<JSRadio>::StaticMethod("onDeleteEvent", &JSInteractableView::JsOnDelete);
132 JSClass<JSRadio>::StaticMethod("onAttach", &JSInteractableView::JsOnAttach);
133 JSClass<JSRadio>::StaticMethod("onAppear", &JSInteractableView::JsOnAppear);
134 JSClass<JSRadio>::StaticMethod("onDetach", &JSInteractableView::JsOnDetach);
135 JSClass<JSRadio>::StaticMethod("onDisAppear", &JSInteractableView::JsOnDisAppear);
136 JSClass<JSRadio>::InheritAndBind<JSViewAbstract>(globalObj);
137 }
138
ParseCheckedObject(const JSCallbackInfo & args,const JSRef<JSVal> & changeEventVal)139 void ParseCheckedObject(const JSCallbackInfo& args, const JSRef<JSVal>& changeEventVal)
140 {
141 CHECK_NULL_VOID(changeEventVal->IsFunction());
142
143 auto jsFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(), JSRef<JSFunc>::Cast(changeEventVal));
144 WeakPtr<NG::FrameNode> targetNode = AceType::WeakClaim(NG::ViewStackProcessor::GetInstance()->GetMainFrameNode());
145 auto onChecked = [execCtx = args.GetExecutionContext(), func = std::move(jsFunc), node = targetNode](bool check) {
146 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
147 ACE_SCORING_EVENT("Radio.onChangeEvent");
148 PipelineContext::SetCallBackNode(node);
149 auto newJSVal = JSRef<JSVal>::Make(ToJSValue(check));
150 func->ExecuteJS(1, &newJSVal);
151 };
152 RadioModel::GetInstance()->SetOnChangeEvent(std::move(onChecked));
153 }
154
Checked(const JSCallbackInfo & info)155 void JSRadio::Checked(const JSCallbackInfo& info)
156 {
157 if (info.Length() < 1 || info.Length() > 2) {
158 return;
159 }
160 bool checked = false;
161 JSRef<JSVal> changeEventVal;
162 auto checkedVal = info[0];
163 if (checkedVal->IsObject()) {
164 JSRef<JSObject> obj = JSRef<JSObject>::Cast(checkedVal);
165 checkedVal = obj->GetProperty("value");
166 changeEventVal = obj->GetProperty("$value");
167 } else if (info.Length() > 1) {
168 changeEventVal = info[1];
169 }
170
171 if (checkedVal->IsBoolean()) {
172 checked = checkedVal->ToBoolean();
173 }
174
175 RadioModel::GetInstance()->SetChecked(checked);
176
177 if (changeEventVal->IsFunction()) {
178 ParseCheckedObject(info, changeEventVal);
179 }
180 }
181
JsSize(const JSCallbackInfo & info)182 void JSRadio::JsSize(const JSCallbackInfo& info)
183 {
184 if (!info[0]->IsObject()) {
185 JSViewAbstract::JsWidth(JSVal::Undefined());
186 JSViewAbstract::JsHeight(JSVal::Undefined());
187 return;
188 }
189 JSRef<JSObject> sizeObj = JSRef<JSObject>::Cast(info[0]);
190 JSViewAbstract::JsWidth(sizeObj->GetProperty(static_cast<int32_t>(ArkUIIndex::WIDTH)));
191 JSViewAbstract::JsHeight(sizeObj->GetProperty(static_cast<int32_t>(ArkUIIndex::HEIGHT)));
192 }
193
JsPadding(const JSCallbackInfo & info)194 void JSRadio::JsPadding(const JSCallbackInfo& info)
195 {
196 if (info.Length() < 1) {
197 return;
198 }
199 NG::PaddingPropertyF oldPadding = GetOldPadding(info);
200 NG::PaddingProperty newPadding = GetNewPadding(info);
201 RadioModel::GetInstance()->SetPadding(oldPadding, newPadding);
202 }
203
JsMargin(const JSCallbackInfo & info)204 void JSRadio::JsMargin(const JSCallbackInfo& info)
205 {
206 RadioModel::GetInstance()->SetIsUserSetMargin(true);
207 JSViewAbstract::JsMargin(info);
208 }
209
GetOldPadding(const JSCallbackInfo & info)210 NG::PaddingPropertyF JSRadio::GetOldPadding(const JSCallbackInfo& info)
211 {
212 NG::PaddingPropertyF padding({ 0.0f, 0.0f, 0.0f, 0.0f });
213 if (info[0]->IsObject()) {
214 JSRef<JSObject> jsObj = JSRef<JSObject>::Cast(info[0]);
215 if (jsObj->HasProperty(static_cast<int32_t>(ArkUIIndex::TOP)) ||
216 jsObj->HasProperty(static_cast<int32_t>(ArkUIIndex::BOTTOM)) ||
217 jsObj->HasProperty(static_cast<int32_t>(ArkUIIndex::LEFT)) ||
218 jsObj->HasProperty(static_cast<int32_t>(ArkUIIndex::RIGHT))) {
219 CalcDimension topDimen = CalcDimension(0.0, DimensionUnit::VP);
220 CalcDimension leftDimen = CalcDimension(0.0, DimensionUnit::VP);
221 CalcDimension rightDimen = CalcDimension(0.0, DimensionUnit::VP);
222 CalcDimension bottomDimen = CalcDimension(0.0, DimensionUnit::VP);
223 ParseJsDimensionVp(jsObj->GetProperty(static_cast<int32_t>(ArkUIIndex::TOP)), topDimen);
224 ParseJsDimensionVp(jsObj->GetProperty(static_cast<int32_t>(ArkUIIndex::LEFT)), leftDimen);
225 ParseJsDimensionVp(jsObj->GetProperty(static_cast<int32_t>(ArkUIIndex::RIGHT)), rightDimen);
226 ParseJsDimensionVp(jsObj->GetProperty(static_cast<int32_t>(ArkUIIndex::BOTTOM)), bottomDimen);
227 if (leftDimen == 0.0_vp) {
228 leftDimen = rightDimen;
229 }
230 if (topDimen == 0.0_vp) {
231 topDimen = bottomDimen;
232 }
233 if (leftDimen == 0.0_vp) {
234 leftDimen = topDimen;
235 }
236
237 padding.left = leftDimen.ConvertToPx();
238 padding.right = rightDimen.ConvertToPx();
239 padding.top = topDimen.ConvertToPx();
240 padding.bottom = bottomDimen.ConvertToPx();
241 return padding;
242 }
243 }
244
245 CalcDimension length;
246 if (!ParseJsDimensionVp(info[0], length)) {
247 return padding;
248 }
249
250 padding.left = length.ConvertToPx();
251 padding.right = length.ConvertToPx();
252 padding.top = length.ConvertToPx();
253 padding.bottom = length.ConvertToPx();
254 return padding;
255 }
256
GetNewPadding(const JSCallbackInfo & info)257 NG::PaddingProperty JSRadio::GetNewPadding(const JSCallbackInfo& info)
258 {
259 NG::PaddingProperty padding({ NG::CalcLength(0.0_vp), NG::CalcLength(0.0_vp), NG::CalcLength(0.0_vp),
260 NG::CalcLength(0.0_vp), std::nullopt, std::nullopt });
261 if (info[0]->IsObject()) {
262 JSRef<JSObject> paddingObj = JSRef<JSObject>::Cast(info[0]);
263 CommonCalcDimension commonCalcDimension;
264 JSViewAbstract::ParseCommonMarginOrPaddingCorner(paddingObj, commonCalcDimension);
265 if (commonCalcDimension.left.has_value() || commonCalcDimension.right.has_value() ||
266 commonCalcDimension.top.has_value() || commonCalcDimension.bottom.has_value()) {
267 return GetPadding(commonCalcDimension.top, commonCalcDimension.bottom, commonCalcDimension.left,
268 commonCalcDimension.right);
269 }
270 }
271 CalcDimension length;
272 if (!ParseJsDimensionVp(info[0], length)) {
273 length.Reset();
274 }
275
276 padding.SetEdges(NG::CalcLength(length.IsNonNegative() ? length : CalcDimension()));
277 return padding;
278 }
279
GetPadding(const std::optional<CalcDimension> & top,const std::optional<CalcDimension> & bottom,const std::optional<CalcDimension> & left,const std::optional<CalcDimension> & right)280 NG::PaddingProperty JSRadio::GetPadding(const std::optional<CalcDimension>& top,
281 const std::optional<CalcDimension>& bottom, const std::optional<CalcDimension>& left,
282 const std::optional<CalcDimension>& right)
283 {
284 NG::PaddingProperty padding({ NG::CalcLength(0.0_vp), NG::CalcLength(0.0_vp), NG::CalcLength(0.0_vp),
285 NG::CalcLength(0.0_vp), std::nullopt, std::nullopt });
286 if (left.has_value() && left.value().IsNonNegative()) {
287 padding.left = NG::CalcLength(left.value());
288 }
289 if (right.has_value() && right.value().IsNonNegative()) {
290 padding.right = NG::CalcLength(right.value());
291 }
292 if (top.has_value() && top.value().IsNonNegative()) {
293 padding.top = NG::CalcLength(top.value());
294 }
295 if (bottom.has_value() && bottom.value().IsNonNegative()) {
296 padding.bottom = NG::CalcLength(bottom.value());
297 }
298 return padding;
299 }
300
JsRadioStyle(const JSCallbackInfo & info)301 void JSRadio::JsRadioStyle(const JSCallbackInfo& info)
302 {
303 auto theme = GetTheme<RadioTheme>();
304 if (!info[0]->IsObject()) {
305 RadioModel::GetInstance()->SetCheckedBackgroundColor(theme->GetActiveColor());
306 RadioModel::GetInstance()->SetUncheckedBorderColor(theme->GetInactiveColor());
307 RadioModel::GetInstance()->SetIndicatorColor(theme->GetPointColor());
308 return;
309 }
310 SetCheckedBackgroundColor(info, theme);
311 SetUncheckedBorderColor(info, theme);
312 SetIndicatorColor(info, theme);
313 }
314
SetCheckedBackgroundColor(const JSCallbackInfo & info,const RefPtr<RadioTheme> & theme)315 void JSRadio::SetCheckedBackgroundColor(const JSCallbackInfo& info, const RefPtr<RadioTheme>& theme)
316 {
317 if (info.Length() < 1 || !info[0]->IsObject()) {
318 return;
319 }
320 JSRef<JSObject> obj = JSRef<JSObject>::Cast(info[0]);
321 JSRef<JSVal> checkedBackgroundColor = obj->GetProperty("checkedBackgroundColor");
322 Color checkedBackgroundColorVal;
323 RefPtr<ResourceObject> backgroundResObj;
324 bool isUserSetBgColor = true;
325 if (!ParseJsColor(checkedBackgroundColor, checkedBackgroundColorVal, backgroundResObj)) {
326 isUserSetBgColor = false;
327 if (!JSRadioTheme::ObtainCheckedBackgroundColor(checkedBackgroundColorVal)) {
328 checkedBackgroundColorVal = theme->GetActiveColor();
329 }
330 }
331 CreateWithResourceObj(backgroundResObj, static_cast<int32_t>(RadioColorType::CHECKED_BACKGROUND_COLOR));
332 RadioModel::GetInstance()->SetCheckedBackgroundColor(checkedBackgroundColorVal);
333 RadioModel::GetInstance()->SetCheckedBackgroundColorSetByUser(isUserSetBgColor);
334 }
335
SetUncheckedBorderColor(const JSCallbackInfo & info,const RefPtr<RadioTheme> & theme)336 void JSRadio::SetUncheckedBorderColor(const JSCallbackInfo& info, const RefPtr<RadioTheme>& theme)
337 {
338 if (info.Length() < 1 || !info[0]->IsObject()) {
339 return;
340 }
341 JSRef<JSObject> obj = JSRef<JSObject>::Cast(info[0]);
342 JSRef<JSVal> uncheckedBorderColor = obj->GetProperty("uncheckedBorderColor");
343 Color uncheckedBorderColorVal;
344 RefPtr<ResourceObject> borderResObj;
345 bool isUserSetUnBorderColor = true;
346 bool isByTheme = false;
347 if (!ParseJsColor(uncheckedBorderColor, uncheckedBorderColorVal, borderResObj)) {
348 isUserSetUnBorderColor = false;
349 if (!JSRadioTheme::ObtainUncheckedBorderColor(uncheckedBorderColorVal)) {
350 isByTheme = true;
351 uncheckedBorderColorVal = theme->GetInactiveColor();
352 }
353 } else {
354 auto frameNode = NG::ViewStackProcessor::GetInstance()->GetMainFrameNode();
355 CHECK_NULL_VOID(frameNode);
356 auto pattern = frameNode->GetPattern<NG::RadioPattern>();
357 CHECK_NULL_VOID(pattern);
358 pattern->SetIsUserSetUncheckBorderColor(true);
359 }
360 CreateWithResourceObj(borderResObj, static_cast<int32_t>(RadioColorType::UNCHECKED_BORDER_COLOR));
361 RadioModel::GetInstance()->SetUncheckedBorderColor(uncheckedBorderColorVal);
362 RadioModel::GetInstance()->SetUncheckedBorderColorSetByUser(isUserSetUnBorderColor);
363 RadioModel::GetInstance()->SetUncheckedBorderColorByJSRadioTheme(isByTheme);
364 }
365
SetIndicatorColor(const JSCallbackInfo & info,const RefPtr<RadioTheme> & theme)366 void JSRadio::SetIndicatorColor(const JSCallbackInfo& info, const RefPtr<RadioTheme>& theme)
367 {
368 if (info.Length() < 1 || !info[0]->IsObject()) {
369 return;
370 }
371 JSRef<JSObject> obj = JSRef<JSObject>::Cast(info[0]);
372 JSRef<JSVal> indicatorColor = obj->GetProperty("indicatorColor");
373 Color indicatorColorVal;
374 RefPtr<ResourceObject> indicatorResObj;
375 bool isUserSetIndicatorColor = true;
376 bool isByTheme = false;
377 if (!ParseJsColor(indicatorColor, indicatorColorVal, indicatorResObj)) {
378 isUserSetIndicatorColor = false;
379 if (!JSRadioTheme::ObtainIndicatorColor(indicatorColorVal)) {
380 isByTheme = true;
381 indicatorColorVal = theme->GetPointColor();
382 }
383 }
384 CreateWithResourceObj(indicatorResObj, static_cast<int32_t>(RadioColorType::INDICATOR_COLOR));
385 RadioModel::GetInstance()->SetIndicatorColor(indicatorColorVal);
386 RadioModel::GetInstance()->SetIndicatorColorSetByUser(isUserSetIndicatorColor);
387 RadioModel::GetInstance()->SetIndicatorColorByJSRadioTheme(isByTheme);
388 }
389
CreateWithResourceObj(const RefPtr<ResourceObject> & resObj,const int32_t colorType)390 void JSRadio::CreateWithResourceObj(const RefPtr<ResourceObject>& resObj, const int32_t colorType)
391 {
392 if (SystemProperties::ConfigChangePerform()) {
393 RadioModel::GetInstance()->CreateWithColorResourceObj(resObj, static_cast<RadioColorType>(colorType));
394 }
395 }
396
JsResponseRegion(const JSCallbackInfo & info)397 void JSRadio::JsResponseRegion(const JSCallbackInfo& info)
398 {
399 if (info.Length() < 1) {
400 return;
401 }
402
403 std::vector<DimensionRect> result;
404 if (!JSViewAbstract::ParseJsResponseRegionArray(info[0], result)) {
405 return;
406 }
407
408 RadioModel::GetInstance()->SetResponseRegion(result);
409 }
410
OnChange(const JSCallbackInfo & args)411 void JSRadio::OnChange(const JSCallbackInfo& args)
412 {
413 if (!args[0]->IsFunction()) {
414 return;
415 }
416 auto jsFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(), JSRef<JSFunc>::Cast(args[0]));
417 WeakPtr<NG::FrameNode> targetNode = AceType::WeakClaim(NG::ViewStackProcessor::GetInstance()->GetMainFrameNode());
418 auto onChange = [execCtx = args.GetExecutionContext(), func = std::move(jsFunc), node = targetNode](bool check) {
419 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
420 ACE_SCORING_EVENT("Radio.onChange");
421 PipelineContext::SetCallBackNode(node);
422 auto newJSVal = JSRef<JSVal>::Make(ToJSValue(check));
423 func->ExecuteJS(1, &newJSVal);
424 UiSessionManager::GetInstance()->ReportComponentChangeEvent("event", "Radio.onChange");
425 };
426 RadioModel::GetInstance()->SetOnChange(std::move(onChange));
427 args.ReturnSelf();
428 }
429
JsOnClick(const JSCallbackInfo & args)430 void JSRadio::JsOnClick(const JSCallbackInfo& args)
431 {
432 if (Container::IsCurrentUseNewPipeline()) {
433 JSViewAbstract::JsOnClick(args);
434 return;
435 }
436 if (!args[0]->IsFunction()) {
437 return;
438 }
439 RadioModel::GetInstance()->SetOnClickEvent(
440 JsEventCallback<void()>(args.GetExecutionContext(), JSRef<JSFunc>::Cast(args[0])));
441
442 args.ReturnSelf();
443 }
444
JsHoverEffect(const JSCallbackInfo & info)445 void JSRadio::JsHoverEffect(const JSCallbackInfo& info)
446 {
447 if (info[0]->IsNumber()) {
448 RadioModel::GetInstance()->SetHoverEffect(static_cast<HoverEffectType>(info[0]->ToNumber<int32_t>()));
449 }
450 }
451 } // namespace OHOS::Ace::Framework
452