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>::InheritAndBind<JSViewAbstract>(globalObj);
91 }
92 
ParseToggleIsOnObject(const JSCallbackInfo & info,const JSRef<JSVal> & changeEventVal)93 void ParseToggleIsOnObject(const JSCallbackInfo& info, const JSRef<JSVal>& changeEventVal)
94 {
95     CHECK_NULL_VOID(changeEventVal->IsFunction());
96 
97     auto jsFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(), JSRef<JSFunc>::Cast(changeEventVal));
98     WeakPtr<NG::FrameNode> targetNode = AceType::WeakClaim(NG::ViewStackProcessor::GetInstance()->GetMainFrameNode());
99     auto onChangeEvent = [execCtx = info.GetExecutionContext(), func = std::move(jsFunc), node = targetNode](
100                              bool isOn) {
101         JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
102         ACE_SCORING_EVENT("Toggle.onChangeEvent");
103         PipelineContext::SetCallBackNode(node);
104         auto newJSVal = JSRef<JSVal>::Make(ToJSValue(isOn));
105         func->ExecuteJS(1, &newJSVal);
106     };
107     ToggleModel::GetInstance()->OnChangeEvent(std::move(onChangeEvent));
108 }
109 
Create(const JSCallbackInfo & info)110 void JSToggle::Create(const JSCallbackInfo& info)
111 {
112     if (!info[0]->IsObject()) {
113         return;
114     }
115 
116     auto paramObject = JSRef<JSObject>::Cast(info[0]);
117     auto type = paramObject->GetProperty("type");
118     int32_t toggleTypeInt = 1;
119     if (type->IsNumber()) {
120         toggleTypeInt = type->ToNumber<int32_t>();
121     }
122     if (toggleTypeInt < 0 || toggleTypeInt > 2) {
123         toggleTypeInt = 1;
124     }
125     toggleType_ = toggleTypeInt;
126     auto tempIsOn = paramObject->GetProperty("isOn");
127     bool isOn = false;
128     JSRef<JSVal> changeEventVal;
129     if (tempIsOn->IsObject()) {
130         JSRef<JSObject> isOnObj = JSRef<JSObject>::Cast(tempIsOn);
131         changeEventVal = isOnObj->GetProperty("changeEvent");
132         auto isOnProperty = isOnObj->GetProperty("value");
133         isOn = isOnProperty->IsBoolean() ? isOnProperty->ToBoolean() : false;
134     } else {
135         isOn = tempIsOn->IsBoolean() ? tempIsOn->ToBoolean() : false;
136     }
137     TAG_LOGD(AceLogTag::ACE_SELECT_COMPONENT, "toggle create type %{public}d isOn %{public}d", toggleTypeInt, isOn);
138     ToggleModel::GetInstance()->Create(NG::ToggleType(toggleTypeInt), isOn);
139     if (!changeEventVal->IsUndefined() && changeEventVal->IsFunction()) {
140         ParseToggleIsOnObject(info, changeEventVal);
141     }
142     JSToggleTheme::ApplyTheme(NG::ToggleType(toggleType_));
143 }
144 
JsWidth(const JSCallbackInfo & info)145 void JSToggle::JsWidth(const JSCallbackInfo& info)
146 {
147     if (info.Length() < 1) {
148         return;
149     }
150     if (AceApplicationInfo::GetInstance().GreatOrEqualTargetAPIVersion(PlatformVersion::VERSION_TWELVE)) {
151         JSViewAbstract::JsWidth(info[0]);
152         return;
153     }
154     JsWidth(info[0]);
155 }
156 
JsWidth(const JSRef<JSVal> & jsValue)157 void JSToggle::JsWidth(const JSRef<JSVal>& jsValue)
158 {
159     auto switchTheme = GetTheme<SwitchTheme>();
160     CHECK_NULL_VOID(switchTheme);
161     auto defaultWidth = switchTheme->GetWidth();
162     auto horizontalPadding = switchTheme->GetHotZoneHorizontalPadding();
163     auto width = defaultWidth - horizontalPadding * 2;
164     if (toggleType_ == 0) {
165         auto checkboxTheme = GetTheme<CheckboxTheme>();
166         CHECK_NULL_VOID(checkboxTheme);
167         defaultWidth = checkboxTheme->GetDefaultWidth();
168         horizontalPadding = checkboxTheme->GetHotZoneHorizontalPadding();
169         width = defaultWidth - horizontalPadding * 2;
170     }
171     CalcDimension value(width);
172     ParseJsDimensionVp(jsValue, value);
173     if (value.IsNegative()) {
174         value = width;
175     }
176     ToggleModel::GetInstance()->SetWidth(value);
177 }
178 
JsHeight(const JSCallbackInfo & info)179 void JSToggle::JsHeight(const JSCallbackInfo& info)
180 {
181     if (info.Length() < 1) {
182         return;
183     }
184     if (AceApplicationInfo::GetInstance().GreatOrEqualTargetAPIVersion(PlatformVersion::VERSION_TWELVE)) {
185         JSViewAbstract::JsHeight(info[0]);
186         return;
187     }
188     JsHeight(info[0]);
189 }
190 
JsHeight(const JSRef<JSVal> & jsValue)191 void JSToggle::JsHeight(const JSRef<JSVal>& jsValue)
192 {
193     auto pipeline = PipelineBase::GetCurrentContext();
194     CHECK_NULL_VOID(pipeline);
195     auto switchTheme = pipeline->GetTheme<SwitchTheme>();
196     CHECK_NULL_VOID(switchTheme);
197     auto defaultHeight = switchTheme->GetHeight();
198     auto verticalPadding = switchTheme->GetHotZoneVerticalPadding();
199     auto height = defaultHeight - verticalPadding * 2;
200     CalcDimension value(height);
201     if (Container::GreatOrEqualAPIVersion(PlatformVersion::VERSION_TEN)) {
202         if (!ParseJsDimensionVpNG(jsValue, value) || value.IsNegative()) {
203             value = height;
204         }
205     } else {
206         ParseJsDimensionVp(jsValue, value);
207         if (value.IsNegative()) {
208             value = height;
209         }
210     }
211     ToggleModel::GetInstance()->SetHeight(value);
212 }
213 
JsResponseRegion(const JSCallbackInfo & info)214 void JSToggle::JsResponseRegion(const JSCallbackInfo& info)
215 {
216     if (!Container::IsCurrentUseNewPipeline()) {
217         JSViewAbstract::JsResponseRegion(info);
218         return;
219     }
220     std::vector<DimensionRect> result;
221     if (!JSViewAbstract::ParseJsResponseRegionArray(info[0], result)) {
222         return;
223     }
224     ToggleModel::GetInstance()->SetResponseRegion(result);
225 }
226 
JsSize(const JSCallbackInfo & info)227 void JSToggle::JsSize(const JSCallbackInfo& info)
228 {
229     if (AceApplicationInfo::GetInstance().GreatOrEqualTargetAPIVersion(PlatformVersion::VERSION_TWELVE)) {
230         JSViewAbstract::JsSize(info);
231         return;
232     }
233     if (!info[0]->IsObject()) {
234         return;
235     }
236 
237     JSRef<JSObject> sizeObj = JSRef<JSObject>::Cast(info[0]);
238     JsWidth(sizeObj->GetProperty("width"));
239     JsHeight(sizeObj->GetProperty("height"));
240 }
241 
OnChange(const JSCallbackInfo & args)242 void JSToggle::OnChange(const JSCallbackInfo& args)
243 {
244     auto jsVal = args[0];
245     if (!jsVal->IsFunction()) {
246         return;
247     }
248     auto jsFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(), JSRef<JSFunc>::Cast(jsVal));
249     WeakPtr<NG::FrameNode> targetNode = AceType::WeakClaim(NG::ViewStackProcessor::GetInstance()->GetMainFrameNode());
250     auto onChange = [execCtx = args.GetExecutionContext(), func = std::move(jsFunc), node = targetNode](bool isOn) {
251         JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
252         ACE_SCORING_EVENT("Toggle.onChange");
253         PipelineContext::SetCallBackNode(node);
254         auto newJSVal = JSRef<JSVal>::Make(ToJSValue(isOn));
255         func->ExecuteJS(1, &newJSVal);
256 #if !defined(PREVIEW) && defined(OHOS_PLATFORM)
257         UiSessionManager::GetInstance().ReportComponentChangeEvent("event", "Toggle.onChange");
258 #endif
259     };
260     ToggleModel::GetInstance()->OnChange(std::move(onChange));
261     args.ReturnSelf();
262 }
263 
SelectedColor(const JSCallbackInfo & info)264 void JSToggle::SelectedColor(const JSCallbackInfo& info)
265 {
266     if (info.Length() < 1) {
267         return;
268     }
269     Color color;
270     std::optional<Color> selectedColor;
271     if (ParseJsColor(info[0], color)) {
272         selectedColor = color;
273     }
274 
275     ToggleModel::GetInstance()->SetSelectedColor(selectedColor);
276 }
277 
SwitchPointColor(const JSCallbackInfo & info)278 void JSToggle::SwitchPointColor(const JSCallbackInfo& info)
279 {
280     if (info.Length() < 1) {
281         return;
282     }
283     Color color;
284     if (!ParseJsColor(info[0], color)) {
285         auto theme = GetTheme<SwitchTheme>();
286         if (theme) {
287             color = theme->GetPointColor();
288         }
289     }
290 
291     ToggleModel::GetInstance()->SetSwitchPointColor(color);
292 }
293 
JsPadding(const JSCallbackInfo & info)294 void JSToggle::JsPadding(const JSCallbackInfo& info)
295 {
296     if (info.Length() < 1) {
297         return;
298     }
299     NG::PaddingPropertyF oldPadding = GetOldPadding(info);
300     NG::PaddingProperty newPadding = GetNewPadding(info);
301     ToggleModel::GetInstance()->SetPadding(oldPadding, newPadding);
302 }
303 
GetOldPadding(const JSCallbackInfo & info)304 NG::PaddingPropertyF JSToggle::GetOldPadding(const JSCallbackInfo& info)
305 {
306     NG::PaddingPropertyF padding({ 0.0f, 0.0f, 0.0f, 0.0f });
307     if (info[0]->IsObject()) {
308         JSRef<JSObject> jsObj = JSRef<JSObject>::Cast(info[0]);
309         if (jsObj->HasProperty("top") || jsObj->HasProperty("bottom")
310             || jsObj->HasProperty("left") || jsObj->HasProperty("right")) {
311             CalcDimension topDimen = CalcDimension(0.0, DimensionUnit::VP);
312             CalcDimension leftDimen = CalcDimension(0.0, DimensionUnit::VP);
313             CalcDimension rightDimen = CalcDimension(0.0, DimensionUnit::VP);
314             CalcDimension bottomDimen = CalcDimension(0.0, DimensionUnit::VP);
315             ParseJsDimensionVp(jsObj->GetProperty("top"), topDimen);
316             ParseJsDimensionVp(jsObj->GetProperty("left"), leftDimen);
317             ParseJsDimensionVp(jsObj->GetProperty("right"), rightDimen);
318             ParseJsDimensionVp(jsObj->GetProperty("bottom"), bottomDimen);
319             if (leftDimen == 0.0_vp) {
320                 leftDimen = rightDimen;
321             }
322             if (topDimen == 0.0_vp) {
323                 topDimen = bottomDimen;
324             }
325             if (leftDimen == 0.0_vp) {
326                 leftDimen = topDimen;
327             }
328 
329             padding.left = leftDimen.ConvertToPx();
330             padding.right = rightDimen.ConvertToPx();
331             padding.top = topDimen.ConvertToPx();
332             padding.bottom = bottomDimen.ConvertToPx();
333             return padding;
334         }
335     }
336 
337     CalcDimension length;
338     if (!ParseJsDimensionVp(info[0], length)) {
339         return padding;
340     }
341 
342     padding.left = length.ConvertToPx();
343     padding.right = length.ConvertToPx();
344     padding.top = length.ConvertToPx();
345     padding.bottom = length.ConvertToPx();
346     return padding;
347 }
348 
GetNewPadding(const JSCallbackInfo & info)349 NG::PaddingProperty JSToggle::GetNewPadding(const JSCallbackInfo& info)
350 {
351     NG::PaddingProperty padding({
352         NG::CalcLength(0.0_vp), NG::CalcLength(0.0_vp), NG::CalcLength(0.0_vp), NG::CalcLength(0.0_vp)
353     });
354     if (info[0]->IsObject()) {
355         std::optional<CalcDimension> left;
356         std::optional<CalcDimension> right;
357         std::optional<CalcDimension> top;
358         std::optional<CalcDimension> bottom;
359         JSRef<JSObject> paddingObj = JSRef<JSObject>::Cast(info[0]);
360 
361         CalcDimension leftDimen;
362         if (ParseJsDimensionVp(paddingObj->GetProperty("left"), leftDimen)) {
363             left = leftDimen;
364         }
365         CalcDimension rightDimen;
366         if (ParseJsDimensionVp(paddingObj->GetProperty("right"), rightDimen)) {
367             right = rightDimen;
368         }
369         CalcDimension topDimen;
370         if (ParseJsDimensionVp(paddingObj->GetProperty("top"), topDimen)) {
371             top = topDimen;
372         }
373         CalcDimension bottomDimen;
374         if (ParseJsDimensionVp(paddingObj->GetProperty("bottom"), bottomDimen)) {
375             bottom = bottomDimen;
376         }
377         if (left.has_value() || right.has_value() || top.has_value() || bottom.has_value()) {
378             padding = GetPadding(top, bottom, left, right);
379             return padding;
380         }
381     }
382     CalcDimension length;
383     if (!ParseJsDimensionVp(info[0], length)) {
384         length.Reset();
385     }
386 
387     padding.SetEdges(NG::CalcLength(length.IsNonNegative() ? length : CalcDimension()));
388     return padding;
389 }
390 
GetPadding(const std::optional<CalcDimension> & top,const std::optional<CalcDimension> & bottom,const std::optional<CalcDimension> & left,const std::optional<CalcDimension> & right)391 NG::PaddingProperty JSToggle::GetPadding(const std::optional<CalcDimension>& top,
392     const std::optional<CalcDimension>& bottom, const std::optional<CalcDimension>& left,
393     const std::optional<CalcDimension>& right)
394 {
395     NG::PaddingProperty padding({
396         NG::CalcLength(0.0_vp), NG::CalcLength(0.0_vp), NG::CalcLength(0.0_vp), NG::CalcLength(0.0_vp)
397     });
398     if (left.has_value() && left.value().IsNonNegative()) {
399         padding.left = NG::CalcLength(left.value());
400     }
401     if (right.has_value() && right.value().IsNonNegative()) {
402         padding.right = NG::CalcLength(right.value());
403     }
404     if (top.has_value() && top.value().IsNonNegative()) {
405         padding.top = NG::CalcLength(top.value());
406     }
407     if (bottom.has_value() && bottom.value().IsNonNegative()) {
408         padding.bottom = NG::CalcLength(bottom.value());
409     }
410     return padding;
411 }
412 
SetBackgroundColor(const JSCallbackInfo & info)413 void JSToggle::SetBackgroundColor(const JSCallbackInfo& info)
414 {
415     Color backgroundColor = Color::TRANSPARENT;
416     bool flag = ParseJsColor(info[0], backgroundColor);
417     if (!Container::IsCurrentUseNewPipeline()) {
418         JSViewAbstract::JsBackgroundColor(info);
419         return;
420     }
421     ToggleModel::GetInstance()->SetBackgroundColor(backgroundColor, flag);
422 }
423 
JsHoverEffect(const JSCallbackInfo & info)424 void JSToggle::JsHoverEffect(const JSCallbackInfo& info)
425 {
426     if (info.Length() > 0 && info[0]->IsNumber()) {
427         ToggleModel::GetInstance()->SetHoverEffect(static_cast<HoverEffectType>(info[0]->ToNumber<int32_t>()));
428     }
429 }
430 
Pop()431 void JSToggle::Pop()
432 {
433     ToggleModel::GetInstance()->Pop();
434 }
435 
SwitchStyle(const JSCallbackInfo & info)436 void JSToggle::SwitchStyle(const JSCallbackInfo& info)
437 {
438     if ((info.Length() < 1) || !info[0]->IsObject()) {
439         return;
440     }
441     JSRef<JSObject> jsObj = JSRef<JSObject>::Cast(info[0]);
442 
443     CalcDimension pointRadius;
444     if (jsObj->HasProperty("pointRadius") &&
445         ParseJsDimensionVpNG(jsObj->GetProperty("pointRadius"), pointRadius, false) && !pointRadius.IsNegative()) {
446         ToggleModel::GetInstance()->SetPointRadius(pointRadius);
447     } else {
448         ToggleModel::GetInstance()->ResetPointRadius();
449     }
450 
451     Color unselectedColor;
452     if (jsObj->HasProperty("unselectedColor") &&
453         ParseJsColor(jsObj->GetProperty("unselectedColor"), unselectedColor)) {
454         ToggleModel::GetInstance()->SetUnselectedColor(unselectedColor);
455     } else {
456         auto theme = GetTheme<SwitchTheme>();
457         if (theme) {
458             unselectedColor = theme->GetInactiveColor();
459         }
460         ToggleModel::GetInstance()->SetUnselectedColor(unselectedColor);
461     }
462 
463     Color pointColor;
464     if (jsObj->HasProperty("pointColor") && ParseJsColor(jsObj->GetProperty("pointColor"), pointColor)) {
465         ToggleModel::GetInstance()->SetSwitchPointColor(pointColor);
466     } else {
467         auto theme = GetTheme<SwitchTheme>();
468         if (theme) {
469             pointColor = theme->GetPointColor();
470         }
471         ToggleModel::GetInstance()->SetSwitchPointColor(pointColor);
472     }
473 
474     CalcDimension trackRadius;
475     if (jsObj->HasProperty("trackBorderRadius") &&
476         ParseJsDimensionVpNG(jsObj->GetProperty("trackBorderRadius"), trackRadius, false) &&
477         !trackRadius.IsNegative()) {
478         ToggleModel::GetInstance()->SetTrackBorderRadius(trackRadius);
479     } else {
480         ToggleModel::GetInstance()->ResetTrackBorderRadius();
481     }
482 }
483 
JsRadius(const JSCallbackInfo & info)484 void JSToggle::JsRadius(const JSCallbackInfo& info)
485 {
486     CalcDimension radius;
487     // when toggle equels button should follow button model.
488     if (static_cast<NG::ToggleType>(toggleType_) == NG::ToggleType::BUTTON) {
489         JSButton::JsRadius(info);
490     } else {
491         JSViewAbstract::JsBorderRadius(info);
492     }
493 }
494 } // namespace OHOS::Ace::Framework
495