1 /*
2  * Copyright (c) 2021 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_toggle.h"
17 
18 #include <cstddef>
19 #include <string>
20 #if !defined(PREVIEW) && defined(OHOS_PLATFORM)
21 #include "interfaces/inner_api/ui_session/ui_session_manager.h"
22 #endif
23 
24 #include "base/log/ace_scoring_log.h"
25 #include "bridge/declarative_frontend/jsview/js_view_abstract.h"
26 #include "bridge/declarative_frontend/jsview/js_button.h"
27 #include "bridge/declarative_frontend/jsview/models/toggle_model_impl.h"
28 #include "bridge/declarative_frontend/ark_theme/theme_apply/js_toggle_theme.h"
29 #include "core/common/container.h"
30 #include "core/components/common/properties/color.h"
31 #include "core/components/toggle/toggle_theme.h"
32 #include "core/components_ng/base/view_stack_processor.h"
33 #include "core/components_ng/pattern/button/toggle_button_model_ng.h"
34 #include "core/components_ng/pattern/toggle/toggle_model_ng.h"
35 
36 namespace OHOS::Ace {
37 
38 std::unique_ptr<ToggleModel> ToggleModel::instance_ = nullptr;
39 std::mutex ToggleModel::mutex_;
40 
GetInstance()41 ToggleModel* ToggleModel::GetInstance()
42 {
43     if (!instance_) {
44         std::lock_guard<std::mutex> lock(mutex_);
45         if (!instance_) {
46 #ifdef NG_BUILD
47             instance_.reset(new NG::ToggleModelNG());
48 #else
49             if (Container::IsCurrentUseNewPipeline()) {
50                 instance_.reset(new NG::ToggleModelNG());
51             } else {
52                 instance_.reset(new Framework::ToggleModelImpl());
53             }
54 #endif
55         }
56     }
57     return instance_.get();
58 }
59 
60 } // namespace OHOS::Ace
61 
62 namespace OHOS::Ace::Framework {
63 int32_t JSToggle::toggleType_ = 1;
JSBind(BindingTarget globalObj)64 void JSToggle::JSBind(BindingTarget globalObj)
65 {
66     JSClass<JSToggle>::Declare("Toggle");
67     JSClass<JSToggle>::StaticMethod("create", &JSToggle::Create);
68     JSClass<JSToggle>::StaticMethod("onChange", &JSToggle::OnChange);
69     JSClass<JSToggle>::StaticMethod("selectedColor", &JSToggle::SelectedColor);
70     JSClass<JSToggle>::StaticMethod("width", &JSToggle::JsWidth);
71     JSClass<JSToggle>::StaticMethod("height", &JSToggle::JsHeight);
72     JSClass<JSToggle>::StaticMethod("responseRegion", &JSToggle::JsResponseRegion);
73     JSClass<JSToggle>::StaticMethod("size", &JSToggle::JsSize);
74     JSClass<JSToggle>::StaticMethod("padding", &JSToggle::JsPadding);
75     JSClass<JSToggle>::StaticMethod("pop", &JSToggle::Pop);
76     JSClass<JSToggle>::StaticMethod("switchPointColor", &JSToggle::SwitchPointColor);
77     JSClass<JSToggle>::StaticMethod("backgroundColor", &JSToggle::SetBackgroundColor);
78     JSClass<JSToggle>::StaticMethod("hoverEffect", &JSToggle::JsHoverEffect);
79     JSClass<JSToggle>::StaticMethod("switchStyle", &JSToggle::SwitchStyle);
80     JSClass<JSToggle>::StaticMethod("onTouch", &JSInteractableView::JsOnTouch);
81     JSClass<JSToggle>::StaticMethod("onClick", &JSInteractableView::JsOnClick);
82     JSClass<JSToggle>::StaticMethod("onHover", &JSInteractableView::JsOnHover);
83     JSClass<JSToggle>::StaticMethod("onKeyEvent", &JSInteractableView::JsOnKey);
84     JSClass<JSToggle>::StaticMethod("onDeleteEvent", &JSInteractableView::JsOnDelete);
85     JSClass<JSToggle>::StaticMethod("onAttach", &JSInteractableView::JsOnAttach);
86     JSClass<JSToggle>::StaticMethod("onAppear", &JSInteractableView::JsOnAppear);
87     JSClass<JSToggle>::StaticMethod("onDetach", &JSInteractableView::JsOnDetach);
88     JSClass<JSToggle>::StaticMethod("onDisAppear", &JSInteractableView::JsOnDisAppear);
89     JSClass<JSToggle>::StaticMethod("borderRadius", &JSToggle::JsRadius);
90     JSClass<JSToggle>::StaticMethod("border", &JSToggle::JsBorder);
91     JSClass<JSToggle>::InheritAndBind<JSViewAbstract>(globalObj);
92 }
93 
ParseToggleIsOnObject(const JSCallbackInfo& info, const JSRef<JSVal>& changeEventVal)94 void ParseToggleIsOnObject(const JSCallbackInfo& info, const JSRef<JSVal>& changeEventVal)
95 {
96     CHECK_NULL_VOID(changeEventVal->IsFunction());
97 
98     auto jsFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(), JSRef<JSFunc>::Cast(changeEventVal));
99     WeakPtr<NG::FrameNode> targetNode = AceType::WeakClaim(NG::ViewStackProcessor::GetInstance()->GetMainFrameNode());
100     auto onChangeEvent = [execCtx = info.GetExecutionContext(), func = std::move(jsFunc), node = targetNode](
101                              bool isOn) {
102         JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
103         ACE_SCORING_EVENT("Toggle.onChangeEvent");
104         PipelineContext::SetCallBackNode(node);
105         auto newJSVal = JSRef<JSVal>::Make(ToJSValue(isOn));
106         func->ExecuteJS(1, &newJSVal);
107     };
108     ToggleModel::GetInstance()->OnChangeEvent(std::move(onChangeEvent));
109 }
110 
Create(const JSCallbackInfo& info)111 void JSToggle::Create(const JSCallbackInfo& info)
112 {
113     if (!info[0]->IsObject()) {
114         return;
115     }
116 
117     auto paramObject = JSRef<JSObject>::Cast(info[0]);
118     auto type = paramObject->GetProperty("type");
119     int32_t toggleTypeInt = 1;
120     if (type->IsNumber()) {
121         toggleTypeInt = type->ToNumber<int32_t>();
122     }
123     if (toggleTypeInt < 0 || toggleTypeInt > 2) {
124         toggleTypeInt = 1;
125     }
126     toggleType_ = toggleTypeInt;
127     auto tempIsOn = paramObject->GetProperty("isOn");
128     bool isOn = false;
129     JSRef<JSVal> changeEventVal;
130     if (tempIsOn->IsObject()) {
131         JSRef<JSObject> isOnObj = JSRef<JSObject>::Cast(tempIsOn);
132         changeEventVal = isOnObj->GetProperty("changeEvent");
133         auto isOnProperty = isOnObj->GetProperty("value");
134         isOn = isOnProperty->IsBoolean() ? isOnProperty->ToBoolean() : false;
135     } else {
136         isOn = tempIsOn->IsBoolean() ? tempIsOn->ToBoolean() : false;
137     }
138     TAG_LOGD(AceLogTag::ACE_SELECT_COMPONENT, "toggle create type %{public}d isOn %{public}d", toggleTypeInt, isOn);
139     ToggleModel::GetInstance()->Create(NG::ToggleType(toggleTypeInt), isOn);
140     if (!changeEventVal->IsUndefined() && changeEventVal->IsFunction()) {
141         ParseToggleIsOnObject(info, changeEventVal);
142     }
143     JSToggleTheme::ApplyTheme(NG::ToggleType(toggleType_));
144 }
145 
JsWidth(const JSCallbackInfo& info)146 void JSToggle::JsWidth(const JSCallbackInfo& info)
147 {
148     if (info.Length() < 1) {
149         return;
150     }
151     if (AceApplicationInfo::GetInstance().GreatOrEqualTargetAPIVersion(PlatformVersion::VERSION_TWELVE)) {
152         JSViewAbstract::JsWidth(info[0]);
153         return;
154     }
155     JsWidth(info[0]);
156 }
157 
JsWidth(const JSRef<JSVal>& jsValue)158 void JSToggle::JsWidth(const JSRef<JSVal>& jsValue)
159 {
160     auto switchTheme = GetTheme<SwitchTheme>();
161     CHECK_NULL_VOID(switchTheme);
162     auto defaultWidth = switchTheme->GetWidth();
163     auto horizontalPadding = switchTheme->GetHotZoneHorizontalPadding();
164     auto width = defaultWidth - horizontalPadding * 2;
165     if (toggleType_ == 0) {
166         auto checkboxTheme = GetTheme<CheckboxTheme>();
167         CHECK_NULL_VOID(checkboxTheme);
168         defaultWidth = checkboxTheme->GetDefaultWidth();
169         horizontalPadding = checkboxTheme->GetHotZoneHorizontalPadding();
170         width = defaultWidth - horizontalPadding * 2;
171     }
172     CalcDimension value(width);
173     ParseJsDimensionVp(jsValue, value);
174     if (value.IsNegative()) {
175         value = width;
176     }
177     ToggleModel::GetInstance()->SetWidth(value);
178 }
179 
JsHeight(const JSCallbackInfo& info)180 void JSToggle::JsHeight(const JSCallbackInfo& info)
181 {
182     if (info.Length() < 1) {
183         return;
184     }
185     if (AceApplicationInfo::GetInstance().GreatOrEqualTargetAPIVersion(PlatformVersion::VERSION_TWELVE)) {
186         JSViewAbstract::JsHeight(info[0]);
187         return;
188     }
189     JsHeight(info[0]);
190 }
191 
JsHeight(const JSRef<JSVal>& jsValue)192 void JSToggle::JsHeight(const JSRef<JSVal>& jsValue)
193 {
194     auto pipeline = PipelineBase::GetCurrentContext();
195     CHECK_NULL_VOID(pipeline);
196     auto switchTheme = pipeline->GetTheme<SwitchTheme>();
197     CHECK_NULL_VOID(switchTheme);
198     auto defaultHeight = switchTheme->GetHeight();
199     auto verticalPadding = switchTheme->GetHotZoneVerticalPadding();
200     auto height = defaultHeight - verticalPadding * 2;
201     CalcDimension value(height);
202     if (Container::GreatOrEqualAPIVersion(PlatformVersion::VERSION_TEN)) {
203         if (!ParseJsDimensionVpNG(jsValue, value) || value.IsNegative()) {
204             value = height;
205         }
206     } else {
207         ParseJsDimensionVp(jsValue, value);
208         if (value.IsNegative()) {
209             value = height;
210         }
211     }
212     ToggleModel::GetInstance()->SetHeight(value);
213 }
214 
JsResponseRegion(const JSCallbackInfo& info)215 void JSToggle::JsResponseRegion(const JSCallbackInfo& info)
216 {
217     if (!Container::IsCurrentUseNewPipeline()) {
218         JSViewAbstract::JsResponseRegion(info);
219         return;
220     }
221     std::vector<DimensionRect> result;
222     if (!JSViewAbstract::ParseJsResponseRegionArray(info[0], result)) {
223         return;
224     }
225     ToggleModel::GetInstance()->SetResponseRegion(result);
226 }
227 
JsSize(const JSCallbackInfo& info)228 void JSToggle::JsSize(const JSCallbackInfo& info)
229 {
230     if (AceApplicationInfo::GetInstance().GreatOrEqualTargetAPIVersion(PlatformVersion::VERSION_TWELVE)) {
231         JSViewAbstract::JsSize(info);
232         return;
233     }
234     if (!info[0]->IsObject()) {
235         return;
236     }
237 
238     JSRef<JSObject> sizeObj = JSRef<JSObject>::Cast(info[0]);
239     JsWidth(sizeObj->GetProperty("width"));
240     JsHeight(sizeObj->GetProperty("height"));
241 }
242 
OnChange(const JSCallbackInfo& args)243 void JSToggle::OnChange(const JSCallbackInfo& args)
244 {
245     auto jsVal = args[0];
246     if (!jsVal->IsFunction()) {
247         return;
248     }
249     auto jsFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(), JSRef<JSFunc>::Cast(jsVal));
250     WeakPtr<NG::FrameNode> targetNode = AceType::WeakClaim(NG::ViewStackProcessor::GetInstance()->GetMainFrameNode());
251     auto onChange = [execCtx = args.GetExecutionContext(), func = std::move(jsFunc), node = targetNode](bool isOn) {
252         JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
253         ACE_SCORING_EVENT("Toggle.onChange");
254         PipelineContext::SetCallBackNode(node);
255         auto newJSVal = JSRef<JSVal>::Make(ToJSValue(isOn));
256         func->ExecuteJS(1, &newJSVal);
257 #if !defined(PREVIEW) && defined(OHOS_PLATFORM)
258         UiSessionManager::GetInstance().ReportComponentChangeEvent("event", "Toggle.onChange");
259 #endif
260     };
261     ToggleModel::GetInstance()->OnChange(std::move(onChange));
262     args.ReturnSelf();
263 }
264 
SelectedColor(const JSCallbackInfo& info)265 void JSToggle::SelectedColor(const JSCallbackInfo& info)
266 {
267     if (info.Length() < 1) {
268         return;
269     }
270     Color color;
271     std::optional<Color> selectedColor;
272     if (ParseJsColor(info[0], color)) {
273         selectedColor = color;
274     }
275 
276     ToggleModel::GetInstance()->SetSelectedColor(selectedColor);
277 }
278 
SwitchPointColor(const JSCallbackInfo& info)279 void JSToggle::SwitchPointColor(const JSCallbackInfo& info)
280 {
281     if (info.Length() < 1) {
282         return;
283     }
284     Color color;
285     if (!ParseJsColor(info[0], color)) {
286         auto theme = GetTheme<SwitchTheme>();
287         if (theme) {
288             color = theme->GetPointColor();
289         }
290     }
291 
292     ToggleModel::GetInstance()->SetSwitchPointColor(color);
293 }
294 
JsPadding(const JSCallbackInfo& info)295 void JSToggle::JsPadding(const JSCallbackInfo& info)
296 {
297     if (info.Length() < 1) {
298         return;
299     }
300     NG::PaddingPropertyF oldPadding = GetOldPadding(info);
301     NG::PaddingProperty newPadding = GetNewPadding(info);
302     ToggleModel::GetInstance()->SetPadding(oldPadding, newPadding);
303 }
304 
GetOldPadding(const JSCallbackInfo& info)305 NG::PaddingPropertyF JSToggle::GetOldPadding(const JSCallbackInfo& info)
306 {
307     NG::PaddingPropertyF padding({ 0.0f, 0.0f, 0.0f, 0.0f });
308     if (info[0]->IsObject()) {
309         JSRef<JSObject> jsObj = JSRef<JSObject>::Cast(info[0]);
310         if (jsObj->HasProperty("top") || jsObj->HasProperty("bottom")
311             || jsObj->HasProperty("left") || jsObj->HasProperty("right")) {
312             CalcDimension topDimen = CalcDimension(0.0, DimensionUnit::VP);
313             CalcDimension leftDimen = CalcDimension(0.0, DimensionUnit::VP);
314             CalcDimension rightDimen = CalcDimension(0.0, DimensionUnit::VP);
315             CalcDimension bottomDimen = CalcDimension(0.0, DimensionUnit::VP);
316             ParseJsDimensionVp(jsObj->GetProperty("top"), topDimen);
317             ParseJsDimensionVp(jsObj->GetProperty("left"), leftDimen);
318             ParseJsDimensionVp(jsObj->GetProperty("right"), rightDimen);
319             ParseJsDimensionVp(jsObj->GetProperty("bottom"), bottomDimen);
320             if (leftDimen == 0.0_vp) {
321                 leftDimen = rightDimen;
322             }
323             if (topDimen == 0.0_vp) {
324                 topDimen = bottomDimen;
325             }
326             if (leftDimen == 0.0_vp) {
327                 leftDimen = topDimen;
328             }
329 
330             padding.left = leftDimen.ConvertToPx();
331             padding.right = rightDimen.ConvertToPx();
332             padding.top = topDimen.ConvertToPx();
333             padding.bottom = bottomDimen.ConvertToPx();
334             return padding;
335         }
336     }
337 
338     CalcDimension length;
339     if (!ParseJsDimensionVp(info[0], length)) {
340         return padding;
341     }
342 
343     padding.left = length.ConvertToPx();
344     padding.right = length.ConvertToPx();
345     padding.top = length.ConvertToPx();
346     padding.bottom = length.ConvertToPx();
347     return padding;
348 }
349 
GetNewPadding(const JSCallbackInfo& info)350 NG::PaddingProperty JSToggle::GetNewPadding(const JSCallbackInfo& info)
351 {
352     NG::PaddingProperty padding({
353         NG::CalcLength(0.0_vp), NG::CalcLength(0.0_vp), NG::CalcLength(0.0_vp), NG::CalcLength(0.0_vp)
354     });
355     if (info[0]->IsObject()) {
356         JSRef<JSObject> paddingObj = JSRef<JSObject>::Cast(info[0]);
357         CommonCalcDimension commonCalcDimension;
358         ParseCommonMarginOrPaddingCorner(paddingObj, commonCalcDimension);
359         if (commonCalcDimension.left.has_value() || commonCalcDimension.right.has_value() ||
360             commonCalcDimension.top.has_value() || commonCalcDimension.bottom.has_value()) {
361             padding = GetPadding(commonCalcDimension.top, commonCalcDimension.bottom, commonCalcDimension.left,
362                 commonCalcDimension.right);
363             return padding;
364         }
365     }
366     CalcDimension length;
367     if (!ParseJsDimensionVp(info[0], length)) {
368         length.Reset();
369     }
370 
371     padding.SetEdges(NG::CalcLength(length.IsNonNegative() ? length : CalcDimension()));
372     return padding;
373 }
374 
GetPadding(const std::optional<CalcDimension>& top, const std::optional<CalcDimension>& bottom, const std::optional<CalcDimension>& left, const std::optional<CalcDimension>& right)375 NG::PaddingProperty JSToggle::GetPadding(const std::optional<CalcDimension>& top,
376     const std::optional<CalcDimension>& bottom, const std::optional<CalcDimension>& left,
377     const std::optional<CalcDimension>& right)
378 {
379     NG::PaddingProperty padding({
380         NG::CalcLength(0.0_vp), NG::CalcLength(0.0_vp), NG::CalcLength(0.0_vp), NG::CalcLength(0.0_vp)
381     });
382     if (left.has_value() && left.value().IsNonNegative()) {
383         padding.left = NG::CalcLength(left.value());
384     }
385     if (right.has_value() && right.value().IsNonNegative()) {
386         padding.right = NG::CalcLength(right.value());
387     }
388     if (top.has_value() && top.value().IsNonNegative()) {
389         padding.top = NG::CalcLength(top.value());
390     }
391     if (bottom.has_value() && bottom.value().IsNonNegative()) {
392         padding.bottom = NG::CalcLength(bottom.value());
393     }
394     return padding;
395 }
396 
SetBackgroundColor(const JSCallbackInfo& info)397 void JSToggle::SetBackgroundColor(const JSCallbackInfo& info)
398 {
399     Color backgroundColor = Color::TRANSPARENT;
400     bool flag = ParseJsColor(info[0], backgroundColor);
401     if (!Container::IsCurrentUseNewPipeline()) {
402         JSViewAbstract::JsBackgroundColor(info);
403         return;
404     }
405     ToggleModel::GetInstance()->SetBackgroundColor(backgroundColor, flag);
406 }
407 
JsHoverEffect(const JSCallbackInfo& info)408 void JSToggle::JsHoverEffect(const JSCallbackInfo& info)
409 {
410     if (info.Length() > 0 && info[0]->IsNumber()) {
411         ToggleModel::GetInstance()->SetHoverEffect(static_cast<HoverEffectType>(info[0]->ToNumber<int32_t>()));
412     }
413 }
414 
Pop()415 void JSToggle::Pop()
416 {
417     ToggleModel::GetInstance()->Pop();
418 }
419 
SwitchStyle(const JSCallbackInfo& info)420 void JSToggle::SwitchStyle(const JSCallbackInfo& info)
421 {
422     if ((info.Length() < 1) || !info[0]->IsObject()) {
423         return;
424     }
425     JSRef<JSObject> jsObj = JSRef<JSObject>::Cast(info[0]);
426 
427     CalcDimension pointRadius;
428     if (jsObj->HasProperty("pointRadius") &&
429         ParseJsDimensionVpNG(jsObj->GetProperty("pointRadius"), pointRadius, false) && !pointRadius.IsNegative()) {
430         ToggleModel::GetInstance()->SetPointRadius(pointRadius);
431     } else {
432         ToggleModel::GetInstance()->ResetPointRadius();
433     }
434 
435     Color unselectedColor;
436     if (jsObj->HasProperty("unselectedColor") &&
437         ParseJsColor(jsObj->GetProperty("unselectedColor"), unselectedColor)) {
438         ToggleModel::GetInstance()->SetUnselectedColor(unselectedColor);
439     } else {
440         auto theme = GetTheme<SwitchTheme>();
441         if (theme) {
442             unselectedColor = theme->GetInactiveColor();
443         }
444         ToggleModel::GetInstance()->SetUnselectedColor(unselectedColor);
445     }
446 
447     Color pointColor;
448     if (jsObj->HasProperty("pointColor") && ParseJsColor(jsObj->GetProperty("pointColor"), pointColor)) {
449         ToggleModel::GetInstance()->SetSwitchPointColor(pointColor);
450     } else {
451         auto theme = GetTheme<SwitchTheme>();
452         if (theme) {
453             pointColor = theme->GetPointColor();
454         }
455         ToggleModel::GetInstance()->SetSwitchPointColor(pointColor);
456     }
457 
458     CalcDimension trackRadius;
459     if (jsObj->HasProperty("trackBorderRadius") &&
460         ParseJsDimensionVpNG(jsObj->GetProperty("trackBorderRadius"), trackRadius, false) &&
461         !trackRadius.IsNegative()) {
462         ToggleModel::GetInstance()->SetTrackBorderRadius(trackRadius);
463     } else {
464         ToggleModel::GetInstance()->ResetTrackBorderRadius();
465     }
466 }
467 
JsRadius(const JSCallbackInfo& info)468 void JSToggle::JsRadius(const JSCallbackInfo& info)
469 {
470     CalcDimension radius;
471     // when toggle equels button should follow button model.
472     if (static_cast<NG::ToggleType>(toggleType_) == NG::ToggleType::BUTTON) {
473         JSButton::JsRadius(info);
474     } else {
475         JSViewAbstract::JsBorderRadius(info);
476     }
477 }
478 
JsBorder(const JSCallbackInfo& info)479 void JSToggle::JsBorder(const JSCallbackInfo& info)
480 {
481     LOGE("JSToggle::JsBorder");
482     JSViewAbstract::JsBorder(info);
483     if (!info[0]->IsObject()) {
484         return;
485     }
486     JSRef<JSObject> object = JSRef<JSObject>::Cast(info[0]);
487     CalcDimension borderRadius;
488     auto valueRadius = object->GetProperty("radius");
489     if (static_cast<NG::ToggleType>(toggleType_) == NG::ToggleType::BUTTON) {
490         JSButton::JsRadius(valueRadius);
491     }
492 }
493 } // namespace OHOS::Ace::Framework
494