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 "bridge/declarative_frontend/jsview/js_checkbox.h"
17 
18 #include <optional>
19 #include <string>
20 
21 #include "base/log/ace_scoring_log.h"
22 #include "bridge/declarative_frontend/jsview/js_interactable_view.h"
23 #include "bridge/declarative_frontend/jsview/js_view_common_def.h"
24 #include "bridge/declarative_frontend/jsview/models/checkbox_model_impl.h"
25 #include "bridge/declarative_frontend/ark_theme/theme_apply/js_checkbox_theme.h"
26 #include "bridge/declarative_frontend/view_stack_processor.h"
27 #include "core/common/container.h"
28 #include "core/components/checkable/checkable_component.h"
29 #include "core/components_ng/base/view_abstract.h"
30 #include "core/components_ng/base/view_abstract_model.h"
31 #include "core/components_ng/base/view_stack_model.h"
32 #include "core/components_ng/base/view_stack_processor.h"
33 #include "core/components_ng/pattern/checkbox/checkbox_model_ng.h"
34 #include "core/components_v2/inspector/inspector_constants.h"
35 
36 namespace OHOS::Ace {
37 namespace {
38 constexpr float CHECK_BOX_MARK_SIZE_INVALID_VALUE = -1.0f;
39 }
40 std::unique_ptr<CheckBoxModel> CheckBoxModel::instance_ = nullptr;
41 std::mutex CheckBoxModel::mutex_;
42 
GetInstance()43 CheckBoxModel* CheckBoxModel::GetInstance()
44 {
45     if (!instance_) {
46         std::lock_guard<std::mutex> lock(mutex_);
47         if (!instance_) {
48 #ifdef NG_BUILD
49             instance_.reset(new NG::CheckBoxModelNG());
50 #else
51             if (Container::IsCurrentUseNewPipeline()) {
52                 instance_.reset(new NG::CheckBoxModelNG());
53             } else {
54                 instance_.reset(new Framework::CheckBoxModelImpl());
55             }
56 #endif
57         }
58     }
59     return instance_.get();
60 }
61 } // namespace OHOS::Ace
62 
63 namespace OHOS::Ace::Framework {
Create(const JSCallbackInfo & info)64 void JSCheckbox::Create(const JSCallbackInfo& info)
65 {
66     auto checkboxName = std::optional<std::string>("");
67     auto checkboxGroup = std::optional<std::string>("");
68     std::optional<std::function<void()>> customBuilderFunc;
69     if ((info.Length() >= 1) && info[0]->IsObject()) {
70         auto paramObject = JSRef<JSObject>::Cast(info[0]);
71         auto name = paramObject->GetProperty("name");
72         auto group = paramObject->GetProperty("group");
73         if (name->IsString()) {
74             checkboxName = name->ToString();
75         }
76         if (group->IsString()) {
77             checkboxGroup = group->ToString();
78         }
79         auto builderObject = paramObject->GetProperty("indicatorBuilder");
80         if (builderObject->IsFunction()) {
81             auto builderFunc = AceType::MakeRefPtr<JsFunction>(info.This(), JSRef<JSFunc>::Cast(builderObject));
82             CHECK_NULL_VOID(builderFunc);
83             auto targetNode = AceType::WeakClaim(NG::ViewStackProcessor::GetInstance()->GetMainFrameNode());
84             auto callbackFunc = [execCtx = info.GetExecutionContext(),
85                 func = std::move(builderFunc), node = targetNode]() {
86                 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
87                 ACE_SCORING_EVENT("CheckBox.builder");
88                 PipelineContext::SetCallBackNode(node);
89                 func->Execute();
90             };
91             customBuilderFunc = std::move(callbackFunc);
92         }
93     }
94     CheckBoxModel::GetInstance()->Create(checkboxName, checkboxGroup, V2::CHECK_BOX_ETS_TAG);
95     CheckBoxModel::GetInstance()->SetBuilder(customBuilderFunc);
96 
97     JSCheckBoxTheme::ApplyTheme();
98 }
99 
JSBind(BindingTarget globalObj)100 void JSCheckbox::JSBind(BindingTarget globalObj)
101 {
102     JSClass<JSCheckbox>::Declare("Checkbox");
103 
104     JSClass<JSCheckbox>::StaticMethod("create", &JSCheckbox::Create);
105     JSClass<JSCheckbox>::StaticMethod("select", &JSCheckbox::SetSelect);
106     JSClass<JSCheckbox>::StaticMethod("shape", &JSCheckbox::SetCheckboxStyle);
107     JSClass<JSCheckbox>::StaticMethod("onChange", &JSCheckbox::SetOnChange);
108     JSClass<JSCheckbox>::StaticMethod("selectedColor", &JSCheckbox::SelectedColor);
109     JSClass<JSCheckbox>::StaticMethod("unselectedColor", &JSCheckbox::UnSelectedColor);
110     JSClass<JSCheckbox>::StaticMethod("mark", &JSCheckbox::Mark);
111     JSClass<JSCheckbox>::StaticMethod("responseRegion", &JSCheckbox::JsResponseRegion);
112     JSClass<JSCheckbox>::StaticMethod("padding", &JSCheckbox::JsPadding);
113     JSClass<JSCheckbox>::StaticMethod("onClick", &JSInteractableView::JsOnClick);
114     JSClass<JSCheckbox>::StaticMethod("onTouch", &JSInteractableView::JsOnTouch);
115     JSClass<JSCheckbox>::StaticMethod("onKeyEvent", &JSInteractableView::JsOnKey);
116     JSClass<JSCheckbox>::StaticMethod("onDeleteEvent", &JSInteractableView::JsOnDelete);
117     JSClass<JSCheckbox>::StaticMethod("onAttach", &JSInteractableView::JsOnAttach);
118     JSClass<JSCheckbox>::StaticMethod("onAppear", &JSInteractableView::JsOnAppear);
119     JSClass<JSCheckbox>::StaticMethod("onDetach", &JSInteractableView::JsOnDetach);
120     JSClass<JSCheckbox>::StaticMethod("onDisAppear", &JSInteractableView::JsOnDisAppear);
121     JSClass<JSCheckbox>::InheritAndBind<JSViewAbstract>(globalObj);
122 }
123 
ParseSelectObject(const JSCallbackInfo & info,const JSRef<JSVal> & changeEventVal)124 void ParseSelectObject(const JSCallbackInfo& info, const JSRef<JSVal>& changeEventVal)
125 {
126     CHECK_NULL_VOID(changeEventVal->IsFunction());
127 
128     auto jsFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(), JSRef<JSFunc>::Cast(changeEventVal));
129     WeakPtr<NG::FrameNode> targetNode = AceType::WeakClaim(NG::ViewStackProcessor::GetInstance()->GetMainFrameNode());
130     auto changeEvent = [execCtx = info.GetExecutionContext(), func = std::move(jsFunc), node = targetNode](bool param) {
131         JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
132         ACE_SCORING_EVENT("CheckBox.ChangeEvent");
133         PipelineContext::SetCallBackNode(node);
134         auto newJSVal = JSRef<JSVal>::Make(ToJSValue(param));
135         func->ExecuteJS(1, &newJSVal);
136     };
137     CheckBoxModel::GetInstance()->SetChangeEvent(std::move(changeEvent));
138 }
139 
SetSelect(const JSCallbackInfo & info)140 void JSCheckbox::SetSelect(const JSCallbackInfo& info)
141 {
142     auto length = info.Length();
143     if (length < 1 || length > 2) {
144         return;
145     }
146     bool select = false;
147     auto jsSelect = info[0];
148     if (length > 0 && jsSelect->IsBoolean()) {
149         select = jsSelect->ToBoolean();
150     }
151     TAG_LOGD(AceLogTag::ACE_SELECT_COMPONENT, "checkbox set select %{public}d", select);
152     CheckBoxModel::GetInstance()->SetSelect(select);
153     if (length > 1 && info[1]->IsFunction()) {
154         ParseSelectObject(info, info[1]);
155     }
156 }
157 
SetOnChange(const JSCallbackInfo & args)158 void JSCheckbox::SetOnChange(const JSCallbackInfo& args)
159 {
160     if (!args[0]->IsFunction()) {
161         return;
162     }
163     auto jsFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(), JSRef<JSFunc>::Cast(args[0]));
164     WeakPtr<NG::FrameNode> targetNode = AceType::WeakClaim(NG::ViewStackProcessor::GetInstance()->GetMainFrameNode());
165     auto onChange = [execCtx = args.GetExecutionContext(), func = std::move(jsFunc), node = targetNode](bool select) {
166         JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
167         ACE_SCORING_EVENT("CheckBox.onChange");
168         PipelineContext::SetCallBackNode(node);
169         auto newJSVal = JSRef<JSVal>::Make(ToJSValue(select));
170         func->ExecuteJS(1, &newJSVal);
171     };
172     CheckBoxModel::GetInstance()->SetOnChange(onChange);
173     args.ReturnSelf();
174 }
175 
JsResponseRegion(const JSCallbackInfo & info)176 void JSCheckbox::JsResponseRegion(const JSCallbackInfo& info)
177 {
178     if (!Container::IsCurrentUseNewPipeline()) {
179         JSViewAbstract::JsResponseRegion(info);
180         return;
181     }
182     if (info.Length() < 1) {
183         return;
184     }
185     std::vector<DimensionRect> result;
186     if (!JSViewAbstract::ParseJsResponseRegionArray(info[0], result)) {
187         return;
188     }
189     CheckBoxModel::GetInstance()->SetResponseRegion(result);
190 }
191 
JsWidth(const JSCallbackInfo & info)192 void JSCheckbox::JsWidth(const JSCallbackInfo& info)
193 {
194     if (info.Length() < 1) {
195         return;
196     }
197 
198     JsWidth(info[0]);
199 }
200 
JsWidth(const JSRef<JSVal> & jsValue)201 void JSCheckbox::JsWidth(const JSRef<JSVal>& jsValue)
202 {
203     CalcDimension value;
204     ParseJsDimensionVp(jsValue, value);
205     if (value.IsNegative()) {
206         ViewAbstractModel::GetInstance()->ClearWidthOrHeight(true);
207         return;
208     }
209     CheckBoxModel::GetInstance()->SetWidth(value);
210 }
211 
JsHeight(const JSCallbackInfo & info)212 void JSCheckbox::JsHeight(const JSCallbackInfo& info)
213 {
214     if (info.Length() < 1) {
215         return;
216     }
217 
218     JsHeight(info[0]);
219 }
220 
JsHeight(const JSRef<JSVal> & jsValue)221 void JSCheckbox::JsHeight(const JSRef<JSVal>& jsValue)
222 {
223     CalcDimension value;
224     ParseJsDimensionVp(jsValue, value);
225     if (value.IsNegative()) {
226         ViewAbstractModel::GetInstance()->ClearWidthOrHeight(false);
227         return;
228     }
229     CheckBoxModel::GetInstance()->SetHeight(value);
230 }
231 
JsSize(const JSCallbackInfo & info)232 void JSCheckbox::JsSize(const JSCallbackInfo& info)
233 {
234     if (info.Length() < 1) {
235         return;
236     }
237 
238     if (!info[0]->IsObject()) {
239         return;
240     }
241 
242     JSRef<JSObject> sizeObj = JSRef<JSObject>::Cast(info[0]);
243     JsWidth(sizeObj->GetProperty("width"));
244     JsHeight(sizeObj->GetProperty("height"));
245 }
246 
SelectedColor(const JSCallbackInfo & info)247 void JSCheckbox::SelectedColor(const JSCallbackInfo& info)
248 {
249     if (info.Length() < 1) {
250         return;
251     }
252     Color selectedColor;
253     auto theme = GetTheme<CheckboxTheme>();
254     if (!ParseJsColor(info[0], selectedColor)) {
255         selectedColor = theme->GetActiveColor();
256     }
257     CheckBoxModel::GetInstance()->SetSelectedColor(selectedColor);
258 }
259 
UnSelectedColor(const JSCallbackInfo & info)260 void JSCheckbox::UnSelectedColor(const JSCallbackInfo& info)
261 {
262     if (info.Length() < 1) {
263         return;
264     }
265     Color unSelectedColor;
266     auto theme = GetTheme<CheckboxTheme>();
267     if (!ParseJsColor(info[0], unSelectedColor)) {
268         unSelectedColor = theme->GetInactiveColor();
269     }
270 
271     CheckBoxModel::GetInstance()->SetUnSelectedColor(unSelectedColor);
272 }
273 
SetCheckboxStyle(int32_t checkBoxStyle)274 void JSCheckbox::SetCheckboxStyle(int32_t checkBoxStyle)
275 {
276     CheckBoxStyle curCheckBoxStyle = static_cast<CheckBoxStyle>(checkBoxStyle);
277     CheckBoxModel::GetInstance()->SetCheckboxStyle(curCheckBoxStyle);
278 }
Mark(const JSCallbackInfo & info)279 void JSCheckbox::Mark(const JSCallbackInfo& info)
280 {
281     auto theme = GetTheme<CheckboxTheme>();
282     if (!info[0]->IsObject()) {
283         CheckBoxModel::GetInstance()->SetCheckMarkColor(theme->GetPointColor());
284         CheckBoxModel::GetInstance()->SetCheckMarkSize(Dimension(CHECK_BOX_MARK_SIZE_INVALID_VALUE));
285         CheckBoxModel::GetInstance()->SetCheckMarkWidth(theme->GetCheckStroke());
286         return;
287     }
288 
289     auto markObj = JSRef<JSObject>::Cast(info[0]);
290     auto strokeColorValue = markObj->GetProperty("strokeColor");
291     Color strokeColor = theme->GetPointColor();
292     if (!ParseJsColor(strokeColorValue, strokeColor)) {
293         JSCheckBoxTheme::ObtainCheckMarkColor(strokeColor);
294     }
295     CheckBoxModel::GetInstance()->SetCheckMarkColor(strokeColor);
296     auto sizeValue = markObj->GetProperty("size");
297     CalcDimension size;
298     if ((ParseJsDimensionVp(sizeValue, size)) && (size.Unit() != DimensionUnit::PERCENT) && (size.ConvertToVp() >= 0)) {
299         CheckBoxModel::GetInstance()->SetCheckMarkSize(size);
300     } else {
301         CheckBoxModel::GetInstance()->SetCheckMarkSize(Dimension(CHECK_BOX_MARK_SIZE_INVALID_VALUE));
302     }
303 
304     auto strokeWidthValue = markObj->GetProperty("strokeWidth");
305     CalcDimension strokeWidth;
306     if ((ParseJsDimensionVp(strokeWidthValue, strokeWidth)) && (strokeWidth.Unit() != DimensionUnit::PERCENT) &&
307         (strokeWidth.ConvertToVp() >= 0)) {
308         CheckBoxModel::GetInstance()->SetCheckMarkWidth(strokeWidth);
309     } else {
310         CheckBoxModel::GetInstance()->SetCheckMarkWidth(theme->GetCheckStroke());
311     }
312 }
313 
JsPadding(const JSCallbackInfo & info)314 void JSCheckbox::JsPadding(const JSCallbackInfo& info)
315 {
316     if (info.Length() < 1) {
317         return;
318     }
319     NG::PaddingPropertyF oldPadding({ 0.0f, 0.0f, 0.0f, 0.0f });
320     bool flag = GetOldPadding(info, oldPadding);
321     NG::PaddingProperty newPadding = GetNewPadding(info);
322     CheckBoxModel::GetInstance()->SetPadding(oldPadding, newPadding, flag);
323 }
324 
GetOldPadding(const JSCallbackInfo & info,NG::PaddingPropertyF & padding)325 bool JSCheckbox::GetOldPadding(const JSCallbackInfo& info, NG::PaddingPropertyF& padding)
326 {
327     if (info[0]->IsObject()) {
328         JSRef<JSObject> jsObj = JSRef<JSObject>::Cast(info[0]);
329         if (jsObj->HasProperty("top") || jsObj->HasProperty("bottom")
330             || jsObj->HasProperty("left") || jsObj->HasProperty("right")) {
331             CalcDimension topDimen = CalcDimension(0.0, DimensionUnit::VP);
332             CalcDimension leftDimen = CalcDimension(0.0, DimensionUnit::VP);
333             CalcDimension rightDimen = CalcDimension(0.0, DimensionUnit::VP);
334             CalcDimension bottomDimen = CalcDimension(0.0, DimensionUnit::VP);
335             ParseJsDimensionVp(jsObj->GetProperty("top"), topDimen);
336             ParseJsDimensionVp(jsObj->GetProperty("left"), leftDimen);
337             ParseJsDimensionVp(jsObj->GetProperty("right"), rightDimen);
338             ParseJsDimensionVp(jsObj->GetProperty("bottom"), bottomDimen);
339             if (leftDimen == 0.0_vp) {
340                 leftDimen = rightDimen;
341             }
342             if (topDimen == 0.0_vp) {
343                 topDimen = bottomDimen;
344             }
345             if (leftDimen == 0.0_vp) {
346                 leftDimen = topDimen;
347             }
348 
349             padding.left = leftDimen.ConvertToPx();
350             padding.right = rightDimen.ConvertToPx();
351             padding.top = topDimen.ConvertToPx();
352             padding.bottom = bottomDimen.ConvertToPx();
353             return true;
354         }
355     }
356 
357     CalcDimension length;
358     if (!ParseJsDimensionVp(info[0], length)) {
359         return false;
360     }
361 
362     padding.left = length.ConvertToPx();
363     padding.right = length.ConvertToPx();
364     padding.top = length.ConvertToPx();
365     padding.bottom = length.ConvertToPx();
366     return true;
367 }
368 
GetNewPadding(const JSCallbackInfo & info)369 NG::PaddingProperty JSCheckbox::GetNewPadding(const JSCallbackInfo& info)
370 {
371     NG::PaddingProperty padding({
372         NG::CalcLength(0.0_vp), NG::CalcLength(0.0_vp), NG::CalcLength(0.0_vp), NG::CalcLength(0.0_vp)
373     });
374     if (info[0]->IsObject()) {
375         std::optional<CalcDimension> left;
376         std::optional<CalcDimension> right;
377         std::optional<CalcDimension> top;
378         std::optional<CalcDimension> bottom;
379         JSRef<JSObject> paddingObj = JSRef<JSObject>::Cast(info[0]);
380 
381         CalcDimension leftDimen;
382         if (ParseJsDimensionVp(paddingObj->GetProperty("left"), leftDimen)) {
383             left = leftDimen;
384         }
385         CalcDimension rightDimen;
386         if (ParseJsDimensionVp(paddingObj->GetProperty("right"), rightDimen)) {
387             right = rightDimen;
388         }
389         CalcDimension topDimen;
390         if (ParseJsDimensionVp(paddingObj->GetProperty("top"), topDimen)) {
391             top = topDimen;
392         }
393         CalcDimension bottomDimen;
394         if (ParseJsDimensionVp(paddingObj->GetProperty("bottom"), bottomDimen)) {
395             bottom = bottomDimen;
396         }
397         if (left.has_value() || right.has_value() || top.has_value() || bottom.has_value()) {
398             padding = GetPadding(top, bottom, left, right);
399             return padding;
400         }
401     }
402     CalcDimension length;
403     if (!ParseJsDimensionVp(info[0], length)) {
404         length.Reset();
405     }
406 
407     padding.SetEdges(NG::CalcLength(length.IsNonNegative() ? length : CalcDimension()));
408     return padding;
409 }
410 
GetPadding(const std::optional<CalcDimension> & top,const std::optional<CalcDimension> & bottom,const std::optional<CalcDimension> & left,const std::optional<CalcDimension> & right)411 NG::PaddingProperty JSCheckbox::GetPadding(const std::optional<CalcDimension>& top,
412     const std::optional<CalcDimension>& bottom, const std::optional<CalcDimension>& left,
413     const std::optional<CalcDimension>& right)
414 {
415     NG::PaddingProperty padding({
416         NG::CalcLength(0.0_vp), NG::CalcLength(0.0_vp), NG::CalcLength(0.0_vp), NG::CalcLength(0.0_vp)
417     });
418     if (left.has_value()) {
419         if (left.value().Unit() == DimensionUnit::CALC) {
420             padding.left =
421                 NG::CalcLength(left.value().IsNonNegative() ? left.value().CalcValue() : CalcDimension().CalcValue());
422         } else {
423             padding.left = NG::CalcLength(left.value().IsNonNegative() ? left.value() : CalcDimension());
424         }
425     }
426     if (right.has_value()) {
427         if (right.value().Unit() == DimensionUnit::CALC) {
428             padding.right =
429                 NG::CalcLength(right.value().IsNonNegative() ? right.value().CalcValue() : CalcDimension().CalcValue());
430         } else {
431             padding.right = NG::CalcLength(right.value().IsNonNegative() ? right.value() : CalcDimension());
432         }
433     }
434     if (top.has_value()) {
435         if (top.value().Unit() == DimensionUnit::CALC) {
436             padding.top =
437                 NG::CalcLength(top.value().IsNonNegative() ? top.value().CalcValue() : CalcDimension().CalcValue());
438         } else {
439             padding.top = NG::CalcLength(top.value().IsNonNegative() ? top.value() : CalcDimension());
440         }
441     }
442     if (bottom.has_value()) {
443         if (bottom.value().Unit() == DimensionUnit::CALC) {
444             padding.bottom = NG::CalcLength(
445                 bottom.value().IsNonNegative() ? bottom.value().CalcValue() : CalcDimension().CalcValue());
446         } else {
447             padding.bottom = NG::CalcLength(bottom.value().IsNonNegative() ? bottom.value() : CalcDimension());
448         }
449     }
450     return padding;
451 }
452 } // namespace OHOS::Ace::Framework
453